From c9917f1c053bfa34dd207d0ac8dc157a09df9316 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 29 May 2023 20:27:46 +0300 Subject: [PATCH 01/58] Updated on 2026-08-14 --- .../tangem/tap/features/demo/DemoHelper.kt | 13 ++-- .../wallet/saltPay/GnosisRegistrator.kt | 14 ++-- .../features/send/redux/SendScreenAction.kt | 3 +- .../redux/middlewares/RequestFeeMiddleware.kt | 20 +++-- .../send/redux/reducers/FeeReducer.kt | 76 ++++++++++--------- .../features/send/redux/states/FeeState.kt | 3 +- .../CurrencyExchangeManager.kt | 7 +- .../tap/proxy/TransactionManagerImpl.kt | 73 ++++++++++++------ 8 files changed, 128 insertions(+), 81 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 cccc4167ae..8191c1016f 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 @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.TransactionSender import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.toBlockchainSdkError +import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.CompletionResult @@ -84,14 +85,14 @@ object DemoHelper { class DemoTransactionSender(private val walletManager: WalletManager) : TransactionSender { - override suspend fun getFee(amount: Amount, destination: String): Result> { + override suspend fun getFee(amount: Amount, destination: String): Result { val blockchain = walletManager.wallet.blockchain return Result.Success( - listOf( - Amount(0.0001.toBigDecimal(), blockchain), - Amount(0.0002.toBigDecimal(), blockchain), - Amount(0.0003.toBigDecimal(), blockchain), - ), + TransactionFee.Choosable( + minimum = Amount(0.0001.toBigDecimal(), blockchain), + normal = Amount(0.0002.toBigDecimal(), blockchain), + priority = Amount(0.0003.toBigDecimal(), blockchain) + ) ) } 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 b5dfe53817..4eab5e464d 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 @@ -9,6 +9,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchain.extensions.successOr @@ -205,13 +206,16 @@ class GnosisRegistrator( } @Suppress("MagicNumber") - private fun Result>.extractFeeAmount(): Result { + 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]) + when(data) { + is TransactionFee.Single -> { + Result.Failure(BlockchainSdkError.FailedToLoadFee) + } + is TransactionFee.Choosable -> { + Result.Success((data as TransactionFee.Choosable).normal) + } } } is Result.Failure -> this 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 719cd930c3..8034231609 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 @@ -4,6 +4,7 @@ import com.tangem.Message import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.core.TangemSdkError import com.tangem.tap.common.redux.ErrorAction import com.tangem.tap.common.redux.StateDialog @@ -127,7 +128,7 @@ sealed class FeeAction : SendScreenAction { object RequestFee : FeeAction() sealed class FeeCalculation : FeeAction() { - data class SetFeeResult(val fee: List) : FeeCalculation() + data class SetFeeResult(val fee: TransactionFee) : FeeCalculation() object ClearResult : FeeCalculation() } 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 8a0b096c4c..49749c36f8 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 @@ -3,6 +3,7 @@ package com.tangem.tap.features.send.redux.middlewares import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.TransactionSender +import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.common.extensions.isZero import com.tangem.tap.common.redux.AppState @@ -53,15 +54,18 @@ class RequestFeeMiddleware { val result = feeResult.data // val result = FeeMock.getFee(walletManager.wallet.blockchain) dispatch(FeeAction.FeeCalculation.SetFeeResult(result)) - if (result.size == 1) { - val fee = result[0].value ?: BigDecimal.ZERO - if (fee.isZero()) { - dispatch(FeeAction.ChangeLayoutVisibility(main = false)) - } else { - dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = false)) + when(result) { + is TransactionFee.Single -> { + val fee = result.value.value ?: BigDecimal.ZERO + if (fee.isZero()) { + dispatch(FeeAction.ChangeLayoutVisibility(main = false)) + } else { + dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = false)) + } + } + is TransactionFee.Choosable -> { + dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = true)) } - } else { - dispatch(FeeAction.ChangeLayoutVisibility(main = true, chipGroup = true)) } } is Result.Failure -> { 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 d0548132b9..b7f943a4e4 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 @@ -1,6 +1,7 @@ package com.tangem.tap.features.send.redux.reducers import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.tap.features.send.redux.FeeAction import com.tangem.tap.features.send.redux.FeeActionUi import com.tangem.tap.features.send.redux.SendScreenAction @@ -13,6 +14,7 @@ import com.tangem.tap.features.wallet.redux.ProgressState [REDACTED_AUTHOR] */ class FeeReducer : SendInternalReducer { + override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) { is FeeActionUi -> handleUiAction(action, sendState, sendState.feeState) is FeeAction -> handleAction(action, sendState, sendState.feeState) @@ -25,11 +27,13 @@ class FeeReducer : SendInternalReducer { state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible) } is FeeActionUi.ChangeSelectedFee -> { - val currentFee = createValueOfFeeAmount(action.feeType, state.feeList) - state.copy( - selectedFeeType = action.feeType, - currentFee = currentFee, - ) + state.feeList?.let { + val currentFee = createValueOfFeeAmount(action.feeType, it) + state.copy( + selectedFeeType = action.feeType, + currentFee = currentFee, + ) + } ?: state } is FeeActionUi.ChangeIncludeFee -> state.copy(feeIsIncluded = action.isIncluded) } @@ -51,26 +55,29 @@ class FeeReducer : SendInternalReducer { } is FeeAction.FeeCalculation.SetFeeResult -> { val fees = action.fee - if (fees.size == 1) { - val feeType = FeeType.SINGLE - val currentFee = createValueOfFeeAmount(feeType, fees) + when (fees) { + is TransactionFee.Single -> { + val feeType = FeeType.SINGLE + val currentFee = createValueOfFeeAmount(feeType, fees) - state.copy( - selectedFeeType = feeType, - feeList = fees, - currentFee = currentFee, - feeIsApproximate = isFeeApproximate(sendState), - ) - } else { - val feeType = getCurrentFeeType(state) - val currentFee = createValueOfFeeAmount(feeType, fees) + state.copy( + selectedFeeType = feeType, + feeList = fees, + currentFee = currentFee, + feeIsApproximate = isFeeApproximate(sendState), + ) + } + is TransactionFee.Choosable -> { + val feeType = getCurrentFeeType(state) + val currentFee = createValueOfFeeAmount(feeType, fees) - state.copy( - selectedFeeType = feeType, - feeList = fees, - currentFee = currentFee, - feeIsApproximate = isFeeApproximate(sendState), - ) + state.copy( + selectedFeeType = feeType, + feeList = fees, + currentFee = currentFee, + feeIsApproximate = isFeeApproximate(sendState), + ) + } }.copy( progressState = ProgressState.Done, ) @@ -87,17 +94,18 @@ class FeeReducer : SendInternalReducer { return updateLastState(sendState.copy(feeState = result), result) } - private fun createValueOfFeeAmount(feeType: FeeType, list: List?): Amount? { - if (list == null || list.isEmpty()) return null - - return if (list.size == 1) { - list[0] - } else { - when (feeType) { - FeeType.SINGLE -> list[1] - FeeType.LOW -> list[0] - FeeType.NORMAL -> list[1] - FeeType.PRIORITY -> list[2] + private fun createValueOfFeeAmount(feeType: FeeType, transactionFee: TransactionFee): Amount { + return when (transactionFee) { + is TransactionFee.Single -> { + transactionFee.value + } + is TransactionFee.Choosable -> { + when (feeType) { + FeeType.SINGLE -> transactionFee.normal + FeeType.LOW -> transactionFee.minimum + FeeType.NORMAL -> transactionFee.normal + FeeType.PRIORITY -> transactionFee.priority + } } } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt index e9c9472bb4..aa0824a755 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.send.redux.states import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.tap.features.wallet.redux.ProgressState import java.math.BigDecimal @@ -13,7 +14,7 @@ enum class FeeType { data class FeeState( val selectedFeeType: FeeType = FeeType.NORMAL, - val feeList: List? = null, + val feeList: TransactionFee? = null, val currentFee: Amount? = null, val feeIsIncluded: Boolean = false, val feeIsApproximate: Boolean = false, 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 a73937a8ee..6be6ffc898 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 @@ -72,7 +72,7 @@ class CurrencyExchangeManager( return when (action) { Action.Buy -> buyService Action.Sell -> sellService - } as ExchangeUrlBuilder + } } enum class Action { Buy, Sell } @@ -96,12 +96,11 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( val amountToSend = Amount(walletManager.wallet.blockchain) val destinationAddress = token.contractAddress - val feeResult = - walletManager.getFee( + val feeResult = walletManager.getFee( amountToSend, destinationAddress, ) as? Result.Success ?: return - val fee = feeResult.data[0] + val fee = feeResult.data.minimum val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO if (coinValue < fee.value) return 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 dabd3c53ec..9ff4ceecd2 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -6,6 +6,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.blockchains.optimism.OptimismWalletManager import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchain.extensions.isNetworkError @@ -197,24 +198,37 @@ class TransactionManagerImpl( return when (fee) { is Result.Success -> { // for not EVM blockchains set gasLimit ZERO for now - val firstFee = fee.data.firstOrNull() ?: error("no fee found") - val minFee = ProxyFee( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = firstFee), - ) - val normalFee = ProxyFee( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(fee.data.getOrNull(index = 1) ?: firstFee), - ) - val priorityFee = ProxyFee( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(fee.data.getOrNull(index = 2) ?: firstFee), - ) - ProxyFees( - minFee = minFee, - normalFee = normalFee, - priorityFee = priorityFee, - ) + when (fee.data) { + is TransactionFee.Single -> { + val singleFee = ProxyFee( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(amount = (fee.data as TransactionFee.Single).value), + ) + ProxyFees( + minFee = singleFee, + normalFee = singleFee, + priorityFee = singleFee + ) + } + is TransactionFee.Choosable -> { + val setOfThree = fee.data as TransactionFee.Choosable + ProxyFees( + minFee = ProxyFee( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(amount = setOfThree.minimum), + ), + normalFee = ProxyFee( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(amount = setOfThree.normal), + ), + priorityFee = ProxyFee( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(amount = setOfThree.priority), + ) + ) + } + } + } is Result.Failure -> { error(fee.error.message ?: fee.error.customMessage) @@ -263,18 +277,33 @@ class TransactionManagerImpl( } return when (fee) { is Result.Success -> { - val minFee = fee.data.firstOrNull() ?: error("no fee found") + val choosableFee = fee.data + ProxyFees( + minFee = ProxyFee( + gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, + fee = convertToProxyAmount(choosableFee.minimum), + ), + normalFee = ProxyFee( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(amount = choosableFee.normal), + ), + priorityFee = ProxyFee( + gasLimit = BigInteger.ZERO, + fee = convertToProxyAmount(amount = choosableFee.priority), + ) + ) + val minProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(minFee), + fee = convertToProxyAmount(fee.data.minimum), ) val normalProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(fee.data.getOrNull(index = 1) ?: minFee), + fee = convertToProxyAmount(fee.data.normal), ) val priorityProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(fee.data.lastOrNull() ?: minFee), + fee = convertToProxyAmount(fee.data.priority), ) ProxyFees( minFee = minProxyFee, From 5e4d8a9113866bb2ac867b73cf74409b923cf422 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 30 May 2023 14:44:10 +0300 Subject: [PATCH 02/58] Updated on 2026-08-14 --- .../java/com/tangem/tap/features/demo/DemoHelper.kt | 10 +++++++--- .../products/wallet/saltPay/GnosisRegistrator.kt | 3 ++- .../tap/features/send/redux/reducers/FeeReducer.kt | 6 ++++-- .../com/tangem/tap/proxy/TransactionManagerImpl.kt | 3 ++- 4 files changed, 15 insertions(+), 7 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 8191c1016f..56a60b96a3 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 @@ -89,9 +89,9 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact val blockchain = walletManager.wallet.blockchain return Result.Success( TransactionFee.Choosable( - minimum = Amount(0.0001.toBigDecimal(), blockchain), - normal = Amount(0.0002.toBigDecimal(), blockchain), - priority = Amount(0.0003.toBigDecimal(), blockchain) + minimum = Amount(minimumFee, blockchain), + normal = Amount(normalFee, blockchain), + priority = Amount(priorityFee, blockchain) ) ) } @@ -121,5 +121,9 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact companion object { val ID = DemoTransactionSender::class.java.simpleName + + private val minimumFee = 0.0001.toBigDecimal() + private val normalFee = 0.0002.toBigDecimal() + private val priorityFee = 0.0003.toBigDecimal() } } \ No newline at end of file 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 4eab5e464d..fb513d2e11 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 @@ -214,7 +214,8 @@ class GnosisRegistrator( Result.Failure(BlockchainSdkError.FailedToLoadFee) } is TransactionFee.Choosable -> { - Result.Success((data as TransactionFee.Choosable).normal) + val normalFee = (data as TransactionFee.Choosable).normal + Result.Success(normalFee) } } } 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 b7f943a4e4..0c285c2fa9 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 @@ -41,6 +41,7 @@ class FeeReducer : SendInternalReducer { } private fun handleAction(action: FeeAction, sendState: SendState, state: FeeState): SendState { + val result = when (action) { is FeeAction.RequestFee -> { state.copy(progressState = ProgressState.Loading) @@ -54,8 +55,7 @@ class FeeReducer : SendInternalReducer { ) } is FeeAction.FeeCalculation.SetFeeResult -> { - val fees = action.fee - when (fees) { + when (val fees = action.fee) { is TransactionFee.Single -> { val feeType = FeeType.SINGLE val currentFee = createValueOfFeeAmount(feeType, fees) @@ -120,4 +120,6 @@ class FeeReducer : SendInternalReducer { private fun getCurrentFeeType(state: FeeState): FeeType { return if (state.selectedFeeType == FeeType.SINGLE) FeeType.NORMAL else state.selectedFeeType } + + } \ 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 9ff4ceecd2..3d489d18fe 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -200,9 +200,10 @@ class TransactionManagerImpl( // for not EVM blockchains set gasLimit ZERO for now when (fee.data) { is TransactionFee.Single -> { + val amount = (fee.data as TransactionFee.Single).value val singleFee = ProxyFee( gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = (fee.data as TransactionFee.Single).value), + fee = convertToProxyAmount(amount = amount), ) ProxyFees( minFee = singleFee, From c8acba2f3bc6d829788bed918b12d73ad80c181b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 May 2023 13:58:21 +0300 Subject: [PATCH 03/58] Updated on 2026-08-14 --- .../tap/features/send/redux/middlewares/RequestFeeMiddleware.kt | 2 +- .../com/tangem/tap/features/send/redux/reducers/FeeReducer.kt | 2 +- .../main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 49749c36f8..4e68e14449 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 @@ -56,7 +56,7 @@ class RequestFeeMiddleware { dispatch(FeeAction.FeeCalculation.SetFeeResult(result)) when(result) { is TransactionFee.Single -> { - val fee = result.value.value ?: BigDecimal.ZERO + val fee = result.normal.value ?: BigDecimal.ZERO if (fee.isZero()) { dispatch(FeeAction.ChangeLayoutVisibility(main = false)) } else { 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 0c285c2fa9..58506a51be 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 @@ -97,7 +97,7 @@ class FeeReducer : SendInternalReducer { private fun createValueOfFeeAmount(feeType: FeeType, transactionFee: TransactionFee): Amount { return when (transactionFee) { is TransactionFee.Single -> { - transactionFee.value + transactionFee.normal } is TransactionFee.Choosable -> { when (feeType) { 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 3d489d18fe..c1a687a860 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -200,7 +200,7 @@ class TransactionManagerImpl( // for not EVM blockchains set gasLimit ZERO for now when (fee.data) { is TransactionFee.Single -> { - val amount = (fee.data as TransactionFee.Single).value + val amount = (fee.data as TransactionFee.Single).normal val singleFee = ProxyFee( gasLimit = BigInteger.ZERO, fee = convertToProxyAmount(amount = amount), From cc4b2e829952748bc06382b8e599642cecba3eff Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 31 May 2023 18:22:10 +0300 Subject: [PATCH 04/58] Updated on 2026-08-14 --- .../tap/proxy/TransactionManagerImpl.kt | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) 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 c1a687a860..7c511dbceb 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -279,33 +279,20 @@ class TransactionManagerImpl( return when (fee) { is Result.Success -> { val choosableFee = fee.data - ProxyFees( - minFee = ProxyFee( - gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(choosableFee.minimum), - ), - normalFee = ProxyFee( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = choosableFee.normal), - ), - priorityFee = ProxyFee( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = choosableFee.priority), - ) - ) val minProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(fee.data.minimum), + fee = convertToProxyAmount(amount = choosableFee.minimum), ) val normalProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(fee.data.normal), + fee = convertToProxyAmount(amount = choosableFee.normal), ) val priorityProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(fee.data.priority), + fee = convertToProxyAmount(amount = choosableFee.priority), ) + ProxyFees( minFee = minProxyFee, normalFee = normalProxyFee, From 85ea643f3be6eeaa62b0d5caf8785fc861c18e5e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 2 Jun 2023 14:31:43 +0300 Subject: [PATCH 05/58] Updated on 2026-08-14 --- .../features/send/redux/reducers/FeeReducer.kt | 15 ++++++++++----- .../tap/features/send/redux/states/FeeState.kt | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) 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 58506a51be..4c41cf308a 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 @@ -8,6 +8,7 @@ import com.tangem.tap.features.send.redux.SendScreenAction import com.tangem.tap.features.send.redux.states.FeeState import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.SendState +import com.tangem.tap.features.send.redux.states.TransactionExtrasState import com.tangem.tap.features.wallet.redux.ProgressState /** @@ -27,17 +28,21 @@ class FeeReducer : SendInternalReducer { state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible) } is FeeActionUi.ChangeSelectedFee -> { - state.feeList?.let { + state.fees?.let { val currentFee = createValueOfFeeAmount(action.feeType, it) state.copy( selectedFeeType = action.feeType, currentFee = currentFee, ) } ?: state + // TODO? } is FeeActionUi.ChangeIncludeFee -> state.copy(feeIsIncluded = action.isIncluded) } - return updateLastState(sendState.copy(feeState = result), result) + return updateLastState(sendState.copy( + feeState = result, + transactionExtrasState = TransactionExtrasState() + ), result) } private fun handleAction(action: FeeAction, sendState: SendState, state: FeeState): SendState { @@ -62,7 +67,7 @@ class FeeReducer : SendInternalReducer { state.copy( selectedFeeType = feeType, - feeList = fees, + fees = fees, currentFee = currentFee, feeIsApproximate = isFeeApproximate(sendState), ) @@ -73,7 +78,7 @@ class FeeReducer : SendInternalReducer { state.copy( selectedFeeType = feeType, - feeList = fees, + fees = fees, currentFee = currentFee, feeIsApproximate = isFeeApproximate(sendState), ) @@ -84,7 +89,7 @@ class FeeReducer : SendInternalReducer { } FeeAction.FeeCalculation.ClearResult -> { state.copy( - feeList = null, + fees = null, currentFee = null, progressState = ProgressState.Done, ) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt index aa0824a755..d1191cf2a4 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt @@ -14,7 +14,7 @@ enum class FeeType { data class FeeState( val selectedFeeType: FeeType = FeeType.NORMAL, - val feeList: TransactionFee? = null, + val fees: TransactionFee? = null, val currentFee: Amount? = null, val feeIsIncluded: Boolean = false, val feeIsApproximate: Boolean = false, From fa6b33840198278b59cfdebd9f4ce2e47ffed0b2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 2 Jun 2023 17:18:37 +0300 Subject: [PATCH 06/58] Updated on 2026-08-14 --- .../com/tangem/tap/features/demo/DemoHelper.kt | 7 ++++--- .../wallet/saltPay/GnosisRegistrator.kt | 2 +- .../redux/middlewares/RequestFeeMiddleware.kt | 2 +- .../features/send/redux/reducers/FeeReducer.kt | 10 +++++----- .../CurrencyExchangeManager.kt | 2 +- .../tangem/tap/proxy/TransactionManagerImpl.kt | 18 +++++++++--------- 6 files changed, 21 insertions(+), 20 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 56a60b96a3..205029fe66 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 @@ -6,6 +6,7 @@ import com.tangem.blockchain.common.TransactionSender import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.toBlockchainSdkError +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult @@ -89,9 +90,9 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact val blockchain = walletManager.wallet.blockchain return Result.Success( TransactionFee.Choosable( - minimum = Amount(minimumFee, blockchain), - normal = Amount(normalFee, blockchain), - priority = Amount(priorityFee, blockchain) + minimum = Fee(Amount(minimumFee, blockchain)), + normal = Fee(Amount(normalFee, blockchain)), + priority = Fee(Amount(priorityFee, blockchain)) ) ) } 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 fb513d2e11..2205efdac9 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 @@ -215,7 +215,7 @@ class GnosisRegistrator( } is TransactionFee.Choosable -> { val normalFee = (data as TransactionFee.Choosable).normal - Result.Success(normalFee) + Result.Success(normalFee.amount) } } } 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 4e68e14449..1277c0993b 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 @@ -56,7 +56,7 @@ class RequestFeeMiddleware { dispatch(FeeAction.FeeCalculation.SetFeeResult(result)) when(result) { is TransactionFee.Single -> { - val fee = result.normal.value ?: BigDecimal.ZERO + val fee = result.normal.amount.value ?: BigDecimal.ZERO if (fee.isZero()) { dispatch(FeeAction.ChangeLayoutVisibility(main = false)) } else { 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 4c41cf308a..317929dbcf 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 @@ -102,14 +102,14 @@ class FeeReducer : SendInternalReducer { private fun createValueOfFeeAmount(feeType: FeeType, transactionFee: TransactionFee): Amount { return when (transactionFee) { is TransactionFee.Single -> { - transactionFee.normal + transactionFee.normal.amount } is TransactionFee.Choosable -> { when (feeType) { - FeeType.SINGLE -> transactionFee.normal - FeeType.LOW -> transactionFee.minimum - FeeType.NORMAL -> transactionFee.normal - FeeType.PRIORITY -> transactionFee.priority + FeeType.SINGLE -> transactionFee.normal.amount + FeeType.LOW -> transactionFee.minimum.amount + FeeType.NORMAL -> transactionFee.normal.amount + FeeType.PRIORITY -> transactionFee.priority.amount } } } 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 6be6ffc898..8b66738ac9 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 @@ -100,7 +100,7 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( amountToSend, destinationAddress, ) as? Result.Success ?: return - val fee = feeResult.data.minimum + val fee = feeResult.data.minimum.amount val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO if (coinValue < fee.value) return 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 7c511dbceb..a18da39a15 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -200,10 +200,10 @@ class TransactionManagerImpl( // for not EVM blockchains set gasLimit ZERO for now when (fee.data) { is TransactionFee.Single -> { - val amount = (fee.data as TransactionFee.Single).normal + val fee = (fee.data as TransactionFee.Single).normal val singleFee = ProxyFee( gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = amount), + fee = convertToProxyAmount(amount = fee.amount), ) ProxyFees( minFee = singleFee, @@ -212,19 +212,19 @@ class TransactionManagerImpl( ) } is TransactionFee.Choosable -> { - val setOfThree = fee.data as TransactionFee.Choosable + val choosableFee = fee.data as TransactionFee.Choosable ProxyFees( minFee = ProxyFee( gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = setOfThree.minimum), + fee = convertToProxyAmount(amount = choosableFee.minimum.amount), ), normalFee = ProxyFee( gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = setOfThree.normal), + fee = convertToProxyAmount(amount = choosableFee.normal.amount), ), priorityFee = ProxyFee( gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = setOfThree.priority), + fee = convertToProxyAmount(amount = choosableFee.priority.amount), ) ) } @@ -282,15 +282,15 @@ class TransactionManagerImpl( val minProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(amount = choosableFee.minimum), + fee = convertToProxyAmount(amount = choosableFee.minimum.amount), ) val normalProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(amount = choosableFee.normal), + fee = convertToProxyAmount(amount = choosableFee.normal.amount), ) val priorityProxyFee = ProxyFee( gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, - fee = convertToProxyAmount(amount = choosableFee.priority), + fee = convertToProxyAmount(amount = choosableFee.priority.amount), ) ProxyFees( From 564e558fccbe670d645cb742cee37408b37ae76f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 5 Jun 2023 12:23:48 +0300 Subject: [PATCH 07/58] Updated on 2026-08-14 --- .../walletconnect/WalletConnectSdkHelper.kt | 6 +++--- .../wallet/saltPay/GnosisRegistrator.kt | 5 +++-- .../send/redux/middlewares/AmountMiddleware.kt | 2 +- .../send/redux/middlewares/SendMiddleware.kt | 17 +++++++++-------- .../features/send/redux/reducers/FeeReducer.kt | 13 +++++++------ .../tap/features/send/redux/states/FeeState.kt | 5 +++-- .../exchangeServices/CurrencyExchangeManager.kt | 4 ++-- .../tangem/tap/proxy/TransactionManagerImpl.kt | 10 ++++++---- 8 files changed, 34 insertions(+), 28 deletions(-) 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 5ccf53357d..ef6ba6e420 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 @@ -13,6 +13,7 @@ import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionSender import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchain.extensions.hexToBigDecimal @@ -85,7 +86,7 @@ class WalletConnectSdkHelper { val transactionData = TransactionData( amount = Amount(value, wallet.blockchain), - fee = Amount(fee, wallet.blockchain), + fee = Fee(Amount(fee, wallet.blockchain)), sourceAddress = transaction.from, destinationAddress = transaction.to!!, extras = EthereumTransactionExtras( @@ -194,8 +195,7 @@ class WalletConnectSdkHelper { val dataToSign = EthereumUtils.buildTransactionToSign( transactionData = data.transaction, nonce = null, - blockchain = data.walletManager.wallet.blockchain, - gasLimit = null, + blockchain = data.walletManager.wallet.blockchain ) ?: return null val command = SignHashCommand( 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 2205efdac9..ed29935f3e 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 @@ -9,6 +9,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult @@ -139,7 +140,7 @@ class GnosisRegistrator( val compiledEthereumTransaction = walletManager.transactionBuilder.buildApproveToSign( transactionData = walletManager.createTransaction( amount = approveAmount, - fee = feeAmount, + fee = Fee(feeAmount), destination = otpProcessorContractAddress, ), nonce = atomicNonce.getAndIncrement().toBigInteger(), @@ -190,7 +191,7 @@ class GnosisRegistrator( val transactionData = walletManager.createTransferFromTransaction( amount = amount, - fee = hardcodeFeeAmount, + feeAmount = hardcodeFeeAmount, source = addressTreasureSafe, ) val transactionToSign = walletManager.transactionBuilder.buildTransferFromToSign( diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt index 15522e87d2..0564218bcd 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AmountMiddleware.kt @@ -67,7 +67,7 @@ class AmountMiddleware { } val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend(inputCrypto)) - val transactionErrors = walletManager.validateTransaction(amountToSend, sendState.feeState.currentFee) + val transactionErrors = walletManager.validateTransaction(amountToSend, sendState.feeState.currentFee?.amount) val amountFieldErrors = filterErrorsForAmountField(transactionErrors) if (amountFieldErrors.isEmpty()) { dispatch(AmountAction.SetAmountError(null)) 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 2d22340b26..679da154f1 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 @@ -12,6 +12,7 @@ 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.common.transaction.Fee import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard @@ -118,11 +119,11 @@ private fun verifyAndSendTransaction( val card = appState.globalState.scanResponse?.card ?: return val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return val typedAmount = sendState.amountState.amountToExtract ?: return - val feeAmount = sendState.feeState.currentFee ?: return + val currentFee = sendState.feeState.currentFee ?: return val amountToSend = Amount(typedAmount, sendState.getTotalAmountToSend()) - val transactionErrors = walletManager.validateTransaction(amountToSend, feeAmount) + val transactionErrors = walletManager.validateTransaction(amountToSend, currentFee.amount) when { transactionErrors.contains(TransactionError.TezosSendAll) -> { val reduceAmount = walletManager.wallet.blockchain.minimalAmount() @@ -134,7 +135,7 @@ private fun verifyAndSendTransaction( }, sendAllCallback = { sendTransaction( - action, walletManager, amountToSend, feeAmount, destinationAddress, + action, walletManager, amountToSend, currentFee, destinationAddress, sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch, ) @@ -145,7 +146,7 @@ private fun verifyAndSendTransaction( } else -> { sendTransaction( - action, walletManager, amountToSend, feeAmount, destinationAddress, + action, walletManager, amountToSend, currentFee, destinationAddress, sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch, ) } @@ -157,7 +158,7 @@ private fun sendTransaction( action: SendActionUi.SendAmountToRecipient, walletManager: WalletManager, amountToSend: Amount, - feeAmount: Amount, + fee: Fee, destinationAddress: String, transactionExtras: TransactionExtrasState, card: CardDTO, @@ -165,7 +166,7 @@ private fun sendTransaction( dispatch: (Action) -> Unit, ) { dispatch(SendAction.ChangeSendButtonState(ButtonState.PROGRESS)) - var txData = walletManager.createTransaction(amountToSend, feeAmount, destinationAddress) + var txData = walletManager.createTransaction(amountToSend, fee, destinationAddress) transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) } transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) } @@ -185,7 +186,7 @@ private fun sendTransaction( updateFeedbackManagerInfo( walletManager = walletManager, amountToSend = amountToSend, - feeAmount = feeAmount, + feeAmount = fee.amount, destinationAddress = destinationAddress, ) dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error = error)) @@ -266,7 +267,7 @@ private fun sendTransaction( updateFeedbackManagerInfo( walletManager = walletManager, amountToSend = amountToSend, - feeAmount = feeAmount, + feeAmount = fee.amount, destinationAddress = destinationAddress, ) val error = sendResult.error as? BlockchainSdkError ?: return@withMainContext 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 317929dbcf..b2a4e5b947 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 @@ -1,6 +1,7 @@ package com.tangem.tap.features.send.redux.reducers import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.tap.features.send.redux.FeeAction import com.tangem.tap.features.send.redux.FeeActionUi @@ -99,17 +100,17 @@ class FeeReducer : SendInternalReducer { return updateLastState(sendState.copy(feeState = result), result) } - private fun createValueOfFeeAmount(feeType: FeeType, transactionFee: TransactionFee): Amount { + private fun createValueOfFeeAmount(feeType: FeeType, transactionFee: TransactionFee): Fee { return when (transactionFee) { is TransactionFee.Single -> { - transactionFee.normal.amount + transactionFee.normal } is TransactionFee.Choosable -> { when (feeType) { - FeeType.SINGLE -> transactionFee.normal.amount - FeeType.LOW -> transactionFee.minimum.amount - FeeType.NORMAL -> transactionFee.normal.amount - FeeType.PRIORITY -> transactionFee.priority.amount + FeeType.SINGLE -> transactionFee.normal + FeeType.LOW -> transactionFee.minimum + FeeType.NORMAL -> transactionFee.normal + FeeType.PRIORITY -> transactionFee.priority } } } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt index d1191cf2a4..4fd9516f9a 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.send.redux.states import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.tap.features.wallet.redux.ProgressState import java.math.BigDecimal @@ -15,7 +16,7 @@ enum class FeeType { data class FeeState( val selectedFeeType: FeeType = FeeType.NORMAL, val fees: TransactionFee? = null, - val currentFee: Amount? = null, + val currentFee: Fee? = null, val feeIsIncluded: Boolean = false, val feeIsApproximate: Boolean = false, val mainLayoutIsVisible: Boolean = false, @@ -29,5 +30,5 @@ data class FeeState( fun isReady(): Boolean = currentFee != null - fun getCurrentFeeValue(): BigDecimal = currentFee?.value ?: BigDecimal.ZERO + fun getCurrentFeeValue(): BigDecimal = currentFee?.amount?.value ?: BigDecimal.ZERO } \ No newline at end of file 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 8b66738ac9..b04348a966 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 @@ -100,10 +100,10 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( amountToSend, destinationAddress, ) as? Result.Success ?: return - val fee = feeResult.data.minimum.amount + val fee = feeResult.data.minimum val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO - if (coinValue < fee.value) return + if (coinValue < fee.amount.value) return val transaction = walletManager.createTransaction(amountToSend, fee, destinationAddress) 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 a18da39a15..5c18b45cdf 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -6,6 +6,8 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.blockchains.optimism.OptimismWalletManager import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.transaction.EthereumFeeExtras +import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult @@ -99,7 +101,7 @@ class TransactionManagerImpl( ): SendTxResult { val txData = walletManager.createTransaction( amount = amount, - fee = Amount(value = feeAmount, blockchain = blockchain), + fee = Fee(Amount(value = feeAmount, blockchain = blockchain)), destination = destinationAddress, ).copy(hash = dataToSign, extras = createExtras(walletManager, gasLimit, dataToSign)) @@ -281,15 +283,15 @@ class TransactionManagerImpl( val choosableFee = fee.data val minProxyFee = ProxyFee( - gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, + gasLimit = (choosableFee.minimum.extras as EthereumFeeExtras).gasLimit, fee = convertToProxyAmount(amount = choosableFee.minimum.amount), ) val normalProxyFee = ProxyFee( - gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, + gasLimit = (choosableFee.normal.extras as EthereumFeeExtras).gasLimit, fee = convertToProxyAmount(amount = choosableFee.normal.amount), ) val priorityProxyFee = ProxyFee( - gasLimit = walletManager.gasLimit ?: BigInteger.ZERO, + gasLimit = (choosableFee.priority.extras as EthereumFeeExtras).gasLimit, fee = convertToProxyAmount(amount = choosableFee.priority.amount), ) From 3a9073f9f28cc9516f5db4c3a844127513e7eb3a Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 7 Jun 2023 18:40:45 +0300 Subject: [PATCH 08/58] Updated on 2026-08-14 --- .../send/redux/reducers/FeeReducer.kt | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) 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 b2a4e5b947..ba706cf281 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 @@ -29,21 +29,17 @@ class FeeReducer : SendInternalReducer { state.copy(controlsLayoutIsVisible = !state.controlsLayoutIsVisible) } is FeeActionUi.ChangeSelectedFee -> { - state.fees?.let { - val currentFee = createValueOfFeeAmount(action.feeType, it) - state.copy( - selectedFeeType = action.feeType, - currentFee = currentFee, - ) - } ?: state - // TODO? + val currentFee = state.fees?.let { + createValueOfFeeAmount(action.feeType, it) + } + state.copy( + selectedFeeType = action.feeType, + currentFee = currentFee + ) } is FeeActionUi.ChangeIncludeFee -> state.copy(feeIsIncluded = action.isIncluded) } - return updateLastState(sendState.copy( - feeState = result, - transactionExtrasState = TransactionExtrasState() - ), result) + return updateLastState(sendState.copy(feeState = result), result) } private fun handleAction(action: FeeAction, sendState: SendState, state: FeeState): SendState { From 72256ab5c58a82552f87d2ccbfc8eb0326a042ac Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 13 Jun 2023 09:27:20 +0300 Subject: [PATCH 09/58] Updated on 2026-08-14 --- .../tap/domain/walletconnect/WalletConnectSdkHelper.kt | 2 +- .../main/java/com/tangem/tap/features/demo/DemoHelper.kt | 6 +++--- .../products/wallet/saltPay/GnosisRegistrator.kt | 2 +- .../java/com/tangem/tap/proxy/TransactionManagerImpl.kt | 9 ++++----- 4 files changed, 9 insertions(+), 10 deletions(-) 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 ef6ba6e420..a26050a16d 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 @@ -86,7 +86,7 @@ class WalletConnectSdkHelper { val transactionData = TransactionData( amount = Amount(value, wallet.blockchain), - fee = Fee(Amount(fee, wallet.blockchain)), + fee = Fee.CommonFee(Amount(fee, wallet.blockchain)), sourceAddress = transaction.from, destinationAddress = transaction.to!!, extras = EthereumTransactionExtras( 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 205029fe66..2dacf8a93d 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 @@ -90,9 +90,9 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact val blockchain = walletManager.wallet.blockchain return Result.Success( TransactionFee.Choosable( - minimum = Fee(Amount(minimumFee, blockchain)), - normal = Fee(Amount(normalFee, blockchain)), - priority = Fee(Amount(priorityFee, blockchain)) + minimum = Fee.CommonFee(Amount(minimumFee, blockchain)), + normal = Fee.CommonFee(Amount(normalFee, blockchain)), + priority = Fee.CommonFee(Amount(priorityFee, blockchain)) ) ) } 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 ed29935f3e..126dd5d655 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 @@ -140,7 +140,7 @@ class GnosisRegistrator( val compiledEthereumTransaction = walletManager.transactionBuilder.buildApproveToSign( transactionData = walletManager.createTransaction( amount = approveAmount, - fee = Fee(feeAmount), + fee = Fee.CommonFee(feeAmount), destination = otpProcessorContractAddress, ), nonce = atomicNonce.getAndIncrement().toBigInteger(), 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 5c18b45cdf..12450303f6 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -6,7 +6,6 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager import com.tangem.blockchain.blockchains.optimism.OptimismWalletManager import com.tangem.blockchain.common.* -import com.tangem.blockchain.common.transaction.EthereumFeeExtras import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.extensions.Result @@ -101,7 +100,7 @@ class TransactionManagerImpl( ): SendTxResult { val txData = walletManager.createTransaction( amount = amount, - fee = Fee(Amount(value = feeAmount, blockchain = blockchain)), + fee = Fee.CommonFee(Amount(value = feeAmount, blockchain = blockchain)), destination = destinationAddress, ).copy(hash = dataToSign, extras = createExtras(walletManager, gasLimit, dataToSign)) @@ -283,15 +282,15 @@ class TransactionManagerImpl( val choosableFee = fee.data val minProxyFee = ProxyFee( - gasLimit = (choosableFee.minimum.extras as EthereumFeeExtras).gasLimit, + gasLimit = (choosableFee.minimum as Fee.EthereumFee).gasLimit, fee = convertToProxyAmount(amount = choosableFee.minimum.amount), ) val normalProxyFee = ProxyFee( - gasLimit = (choosableFee.normal.extras as EthereumFeeExtras).gasLimit, + gasLimit = (choosableFee.normal as Fee.EthereumFee).gasLimit, fee = convertToProxyAmount(amount = choosableFee.normal.amount), ) val priorityProxyFee = ProxyFee( - gasLimit = (choosableFee.priority.extras as EthereumFeeExtras).gasLimit, + gasLimit = (choosableFee.priority as Fee.EthereumFee).gasLimit, fee = convertToProxyAmount(amount = choosableFee.priority.amount), ) From 24b9265c738f799942c5fba4f852b1dec28a5093 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 14 Jun 2023 23:27:16 +0300 Subject: [PATCH 10/58] Updated on 2026-08-14 --- .../tap/domain/walletconnect/WalletConnectSdkHelper.kt | 2 +- .../main/java/com/tangem/tap/features/demo/DemoHelper.kt | 6 +++--- .../products/wallet/saltPay/GnosisRegistrator.kt | 2 +- .../java/com/tangem/tap/proxy/TransactionManagerImpl.kt | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) 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 a26050a16d..af3a5c19f9 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 @@ -86,7 +86,7 @@ class WalletConnectSdkHelper { val transactionData = TransactionData( amount = Amount(value, wallet.blockchain), - fee = Fee.CommonFee(Amount(fee, wallet.blockchain)), + fee = Fee.Common(Amount(fee, wallet.blockchain)), sourceAddress = transaction.from, destinationAddress = transaction.to!!, extras = EthereumTransactionExtras( 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 2dacf8a93d..83ea251268 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 @@ -90,9 +90,9 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact val blockchain = walletManager.wallet.blockchain return Result.Success( TransactionFee.Choosable( - minimum = Fee.CommonFee(Amount(minimumFee, blockchain)), - normal = Fee.CommonFee(Amount(normalFee, blockchain)), - priority = Fee.CommonFee(Amount(priorityFee, blockchain)) + minimum = Fee.Common(Amount(minimumFee, blockchain)), + normal = Fee.Common(Amount(normalFee, blockchain)), + priority = Fee.Common(Amount(priorityFee, blockchain)) ) ) } 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 126dd5d655..fa982df1c9 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 @@ -140,7 +140,7 @@ class GnosisRegistrator( val compiledEthereumTransaction = walletManager.transactionBuilder.buildApproveToSign( transactionData = walletManager.createTransaction( amount = approveAmount, - fee = Fee.CommonFee(feeAmount), + fee = Fee.Common(feeAmount), destination = otpProcessorContractAddress, ), nonce = atomicNonce.getAndIncrement().toBigInteger(), 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 12450303f6..24b221cc39 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -100,7 +100,7 @@ class TransactionManagerImpl( ): SendTxResult { val txData = walletManager.createTransaction( amount = amount, - fee = Fee.CommonFee(Amount(value = feeAmount, blockchain = blockchain)), + fee = Fee.Common(Amount(value = feeAmount, blockchain = blockchain)), destination = destinationAddress, ).copy(hash = dataToSign, extras = createExtras(walletManager, gasLimit, dataToSign)) @@ -282,15 +282,15 @@ class TransactionManagerImpl( val choosableFee = fee.data val minProxyFee = ProxyFee( - gasLimit = (choosableFee.minimum as Fee.EthereumFee).gasLimit, + gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit, fee = convertToProxyAmount(amount = choosableFee.minimum.amount), ) val normalProxyFee = ProxyFee( - gasLimit = (choosableFee.normal as Fee.EthereumFee).gasLimit, + gasLimit = (choosableFee.normal as Fee.Ethereum).gasLimit, fee = convertToProxyAmount(amount = choosableFee.normal.amount), ) val priorityProxyFee = ProxyFee( - gasLimit = (choosableFee.priority as Fee.EthereumFee).gasLimit, + gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit, fee = convertToProxyAmount(amount = choosableFee.priority.amount), ) From 77680682a64780ad361e09ae946ed713ba7f705b Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 16 Jun 2023 12:25:10 +0300 Subject: [PATCH 11/58] Updated on 2026-08-14 --- .../wallet/redux/reducers/WalletReducer.kt | 3 +- .../wallet/ui/MultipleAddressUiHelper.kt | 28 ++++--------------- gradle/dependencies.toml | 4 +-- 3 files changed, 10 insertions(+), 25 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 a196a6146b..ac44f59413 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 @@ -1,6 +1,7 @@ package com.tangem.tap.features.wallet.redux.reducers import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.address.AddressType import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.models.scan.CardDTO import com.tangem.tap.common.redux.AppState @@ -151,7 +152,7 @@ fun Wallet.createAddressesData(): List { getShareUri(it.value), getExploreUrl(it.value), ) - if (it.type == blockchain.defaultAddressType()) { + if (it.type == AddressType.Default) { listOfAddressData.add(0, addressData) } else { listOfAddressData.add(addressData) 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 67e10ca9a6..3bd06aed6a 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 @@ -1,40 +1,24 @@ package com.tangem.tap.features.wallet.ui -import android.view.View -import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType -import com.tangem.blockchain.blockchains.cardano.CardanoAddressType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressType import com.tangem.wallet.R 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 + AddressType.Legacy -> R.id.chip_legacy + AddressType.Default -> R.id.chip_default } } 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 - } - } + R.id.chip_default -> AddressType.Default + R.id.chip_legacy -> AddressType.Legacy else -> null } } + } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index f7f6cb31df..1274465081 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -74,8 +74,8 @@ reactiveNetwork = "3.0.8" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-225" -#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds +#tangemBlockchainSdk = "develop-225" +tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-249" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds # endregion Tangem From 907bc6335ef877ff7a638d6e2fa62dac43381dea Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 23 Jun 2023 16:39:25 +0300 Subject: [PATCH 12/58] Updated on 2026-08-14 --- .../tangem/tap/domain/extensions/WalletManagerFactory.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 a9de7cb47a..3d65b09b25 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 @@ -35,7 +35,7 @@ fun WalletManagerFactory.makeWalletManagerForApp( val seedKey = wallet.extendedPublicKey return when { scanResponse.cardTypesResolver.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> { - makeTwinWalletManager( + createTwinWalletManager( walletPublicKey = wallet.publicKey, pairPublicKey = scanResponse.secondTwinPublicKey!!.hexToBytes(), blockchain = environmentBlockchain, @@ -51,7 +51,7 @@ fun WalletManagerFactory.makeWalletManagerForApp( val derivedKey = derivedKeys?.get(derivationPath) ?: return null - makeWalletManager( + createWalletManager( blockchain = environmentBlockchain, seedKey = wallet.publicKey, derivedKey = derivedKey, @@ -59,7 +59,7 @@ fun WalletManagerFactory.makeWalletManagerForApp( ) } else -> { - makeWalletManager( + createLegacyWalletManager( blockchain = environmentBlockchain, walletPublicKey = wallet.publicKey, curve = wallet.curve, From 2ab957d18a5d5875ec27bbe80670bd9473309f65 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Jun 2023 20:25:28 +0300 Subject: [PATCH 13/58] Updated on 2026-08-14 --- .../wallet/ui/MultipleAddressUiHelper.kt | 46 ++++++++++--------- .../wallet/ui/WalletDetailsFragment.kt | 2 +- .../wallet/ui/wallet/SingleWalletView.kt | 2 +- 3 files changed, 27 insertions(+), 23 deletions(-) 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 e82c2f9547..b4866eda69 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 @@ -1,20 +1,24 @@ package com.tangem.tap.features.wallet.ui import android.view.View -import com.tangem.blockchain.blockchains.bitcoin.BitcoinAddressType -import com.tangem.blockchain.blockchains.cardano.CardanoAddressType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressType import com.tangem.wallet.R 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 typeToId(type: AddressType, blockchain: Blockchain): Int { + return when (blockchain) { + in blockchainsSupportingSplit -> { + if (type == AddressType.Legacy) { + R.id.chip_legacy + } else { + R.id.chip_default + } + } + else -> { + View.NO_ID + } } } @@ -22,27 +26,27 @@ object MultipleAddressUiHelper { return when (id) { R.id.chip_default -> { when (blockchain) { - Blockchain.Bitcoin, - Blockchain.BitcoinTestnet, - Blockchain.Litecoin, - Blockchain.BitcoinCash, - -> BitcoinAddressType.Segwit - Blockchain.CardanoShelley -> CardanoAddressType.Shelley + in blockchainsSupportingSplit -> AddressType.Default else -> null } } + R.id.chip_legacy -> { when (blockchain) { - Blockchain.Bitcoin, - Blockchain.BitcoinTestnet, - Blockchain.Litecoin, - Blockchain.BitcoinCash, - -> BitcoinAddressType.Legacy - Blockchain.CardanoShelley -> CardanoAddressType.Byron + in blockchainsSupportingSplit -> AddressType.Legacy else -> null } } + else -> null } } + + private val blockchainsSupportingSplit = listOf( + Blockchain.Bitcoin, + Blockchain.BitcoinTestnet, + Blockchain.Litecoin, + Blockchain.BitcoinCash, + Blockchain.CardanoShelley + ) } \ 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 afee65593c..b8f1e06942 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 @@ -390,7 +390,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), SafeSt chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = MultipleAddressUiHelper.typeToId(selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(selectedAddress.type, currency.blockchain) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { _, checkedId -> 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 a97a94e9f3..b3ca7e7f2b 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 @@ -190,7 +190,7 @@ class SingleWalletView : WalletView() { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = MultipleAddressUiHelper.typeToId(primaryWallet.walletAddresses.selectedAddress.type) + val checkedId = MultipleAddressUiHelper.typeToId(primaryWallet.walletAddresses.selectedAddress.type, primaryWallet.currency.blockchain) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> From e3db5621edd0088cb81aa27090e20c23321f8e75 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 27 Jun 2023 23:44:45 +0300 Subject: [PATCH 14/58] Updated on 2026-08-14 --- .../wallet/ui/MultipleAddressUiHelper.kt | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) 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 b4866eda69..7caaabe6e1 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 @@ -22,22 +22,15 @@ object MultipleAddressUiHelper { } } - fun idToType(id: Int, blockchain: Blockchain?): AddressType? { - return when (id) { - R.id.chip_default -> { - when (blockchain) { - in blockchainsSupportingSplit -> AddressType.Default + fun idToType(id: Int, blockchain: Blockchain): AddressType? { + return when (blockchain) { + in blockchainsSupportingSplit -> { + when(id) { + R.id.chip_default -> AddressType.Default + R.id.chip_legacy -> AddressType.Legacy else -> null } } - - R.id.chip_legacy -> { - when (blockchain) { - in blockchainsSupportingSplit -> AddressType.Legacy - else -> null - } - } - else -> null } } @@ -49,4 +42,5 @@ object MultipleAddressUiHelper { Blockchain.BitcoinCash, Blockchain.CardanoShelley ) + } \ No newline at end of file From 59da7f01548f477f12b5372286341c3b7fcd9eb8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 4 Jul 2023 19:20:24 +0300 Subject: [PATCH 15/58] Updated on 2026-08-14 --- .../src/main/assets/configs/feature_toggles_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index ab2ed7ee14..dec9b4f24f 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -1,7 +1,7 @@ [ { "name": "OPTIMISM_SWAP_FEATURE_ENABLED", - "version": "4.3.1" + "version": "1.0.0" }, { "name": "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED", From 2585f9e0e862bfb7b4c953cc696b5a9f9944e6db Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 7 Jul 2023 16:31:20 +0300 Subject: [PATCH 16/58] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../java/com/tangem/tap/TapApplication.kt | 11 +++++++--- .../java/com/tangem/tap/common/images/Coil.kt | 11 ++-------- .../walletconnect/WalletConnectManager.kt | 5 +++-- core/datasource/build.gradle.kts | 1 + .../tangem/datasource/api/common/Retrofit.kt | 20 ++++++++++--------- .../com/tangem/datasource/di/NetworkModule.kt | 4 +--- .../tangem/datasource/utils/HttpClientExt.kt | 11 ++++++---- gradle/dependencies.toml | 4 ++++ 9 files changed, 38 insertions(+), 30 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efe2848d8c..14433e7634 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -139,6 +139,7 @@ dependencies { implementation(deps.kotlin.serialization) implementation(deps.walletConnectCore) implementation(deps.walletConnectWeb3) + implementation(deps.prettyLogger) /** Testing libraries */ testImplementation(deps.test.junit) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 114746ccdb..086d90c904 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.pm.PackageManager import coil.ImageLoader import coil.ImageLoaderFactory +import com.orhanobut.logger.Logger import com.tangem.Log import com.tangem.LogFormat import com.tangem.blockchain.common.BlockchainSdkConfig @@ -14,6 +15,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.featuretoggle.manager.FeatureTogglesManager import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.datasource.api.common.MoshiConverter +import com.tangem.datasource.api.common.createNetworkLoggingInterceptor import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.config.ConfigManager import com.tangem.datasource.config.FeaturesLocalLoader @@ -58,7 +60,6 @@ import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.wallet.BuildConfig import dagger.hilt.android.HiltAndroidApp import kotlinx.coroutines.runBlocking -import okhttp3.logging.HttpLoggingInterceptor import org.rekotlin.Store import timber.log.Timber import javax.inject.Inject @@ -168,7 +169,11 @@ class TapApplication : Application(), ImageLoaderFactory { ) if (BuildConfig.DEBUG) { - Timber.plant(Timber.DebugTree()) + Timber.plant(object : Timber.DebugTree() { + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + Logger.log(priority, tag, message, t) + } + }) } foregroundActivityObserver = ForegroundActivityObserver() @@ -187,7 +192,7 @@ class TapApplication : Application(), ImageLoaderFactory { if (LogConfig.network.blockchainSdkNetwork) { BlockchainSdkRetrofitBuilder.interceptors = listOf( - HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }, + createNetworkLoggingInterceptor() ) } diff --git a/app/src/main/java/com/tangem/tap/common/images/Coil.kt b/app/src/main/java/com/tangem/tap/common/images/Coil.kt index b7305e86dc..0d6d4de675 100644 --- a/app/src/main/java/com/tangem/tap/common/images/Coil.kt +++ b/app/src/main/java/com/tangem/tap/common/images/Coil.kt @@ -4,8 +4,8 @@ import android.content.Context import android.util.Log import coil.ImageLoader import coil.util.Logger +import com.tangem.datasource.api.common.createNetworkLoggingInterceptor import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import timber.log.Timber private const val COIL_LOG_TAG = "COIL" @@ -18,14 +18,7 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL logger(CoilTimberLogger()) okHttpClient { OkHttpClient.Builder() - .addNetworkInterceptor( - HttpLoggingInterceptor { message -> - Timber.tag(COIL_LOG_TAG).d(message) - } - .apply { - level = HttpLoggingInterceptor.Level.BODY - }, - ) + .addNetworkInterceptor(createNetworkLoggingInterceptor()) .build() } } 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 7b80ce1039..99825e2721 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 @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics +import com.tangem.datasource.api.common.createNetworkLoggingInterceptor import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.analytics.events.WalletConnect @@ -36,7 +37,6 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import okhttp3.logging.HttpLoggingInterceptor import timber.log.Timber import java.util.* import java.util.concurrent.TimeUnit @@ -56,8 +56,9 @@ class WalletConnectManager { .addInterceptor(RetryInterceptor()) .build() } + private val interceptor by lazy { - HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } + createNetworkLoggingInterceptor() } private var sessions: MutableMap = mutableMapOf() diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index de4006b63a..114edb01aa 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation(deps.retrofit) implementation(deps.retrofit.moshi) implementation(deps.reactive.network) + implementation(deps.prettyOkHttpLoggingInterceptor) /** Time */ implementation(deps.jodatime) 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 166036783c..72aea8deb2 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 @@ -1,8 +1,10 @@ package com.tangem.datasource.api.common +import android.util.Log +import com.ihsanbal.logging.Level +import com.ihsanbal.logging.LoggingInterceptor import okhttp3.Interceptor import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import java.util.concurrent.TimeUnit @@ -22,15 +24,15 @@ fun createRetrofitInstance( } interceptors.forEach { okHttpBuilder.addInterceptor(it) } - if (logEnabled) okHttpBuilder.addInterceptor(createHttpLoggingInterceptor()) + if (logEnabled) okHttpBuilder.addInterceptor(createNetworkLoggingInterceptor()) - return Retrofit.Builder() - .baseUrl(baseUrl) - .addConverterFactory(MoshiConverter.networkMoshiConverter) - .client(okHttpBuilder.build()) - .build() + return Retrofit.Builder().baseUrl(baseUrl).addConverterFactory(MoshiConverter.networkMoshiConverter) + .client(okHttpBuilder.build()).build() } -private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY +fun createNetworkLoggingInterceptor(): Interceptor { + return LoggingInterceptor.Builder() + .setLevel(Level.BODY) + .log(Log.VERBOSE) + .build() } \ 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 c8ae82faa9..9c11afc771 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 @@ -63,9 +63,7 @@ class NetworkModule { @PromotionOneInch fun providePromotionOneInchApi(authProvider: AuthProvider, @NetworkMoshi moshi: Moshi): PromotionApi { val okClient = OkHttpClient.Builder() - .addHeaders( - AuthenticationHeader(authProvider), - ) + .addHeaders(AuthenticationHeader(authProvider)) .allowLogging() .callTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) .connectTimeout(API_ONE_INCH_TIMEOUT_MS, TimeUnit.MILLISECONDS) 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 index 4fd16a0d1d..384676621a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt @@ -1,10 +1,9 @@ package com.tangem.datasource.utils import com.tangem.datasource.BuildConfig +import com.tangem.datasource.api.common.createNetworkLoggingInterceptor 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 { @@ -26,5 +25,9 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade * * @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 +internal fun OkHttpClient.Builder.allowLogging(): OkHttpClient.Builder = + if (BuildConfig.DEBUG) { + addInterceptor(interceptor = createNetworkLoggingInterceptor()) + } else { + this + } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5fb4bbfb94..9b66d181ca 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -75,6 +75,8 @@ arrow = "1.2.0-RC" reactiveNetwork = "3.0.8" walletConnectCore = "1.15.0" walletConnectWeb3 = "1.8.0" +prettyLogger = "2.2.0" +prettyOkHttpLoggingInterceptor = "3.1.0" # endregion Other libraries # region Tangem @@ -222,4 +224,6 @@ arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" } walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" } walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" } +prettyLogger = { module = "com.orhanobut:logger", version.ref = "prettyLogger" } +prettyOkHttpLoggingInterceptor = { module = "com.github.ihsanbal:LoggingInterceptor", version.ref = "prettyOkHttpLoggingInterceptor" } # endregion Other From eafd243752d2b92a105855378b55f87b09e6d9c2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 7 Jul 2023 19:01:21 +0300 Subject: [PATCH 17/58] Updated on 2026-08-14 --- core/datasource/build.gradle.kts | 3 +-- .../main/java/com/tangem/datasource/api/common/Retrofit.kt | 7 +++++-- .../main/java/com/tangem/datasource/utils/HttpClientExt.kt | 7 ++++--- gradle/dependencies.toml | 5 ++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 114edb01aa..aa45701df5 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -32,11 +32,10 @@ dependencies { implementation(deps.moshi) implementation(deps.moshi.kotlin) implementation(deps.okHttp) - implementation(deps.okHttp.logging) + implementation(deps.okHttp.prettyLogging) implementation(deps.retrofit) implementation(deps.retrofit.moshi) implementation(deps.reactive.network) - implementation(deps.prettyOkHttpLoggingInterceptor) /** Time */ implementation(deps.jodatime) 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 72aea8deb2..3d8b6f3930 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 @@ -26,8 +26,11 @@ fun createRetrofitInstance( if (logEnabled) okHttpBuilder.addInterceptor(createNetworkLoggingInterceptor()) - return Retrofit.Builder().baseUrl(baseUrl).addConverterFactory(MoshiConverter.networkMoshiConverter) - .client(okHttpBuilder.build()).build() + return Retrofit.Builder() + .baseUrl(baseUrl) + .addConverterFactory(MoshiConverter.networkMoshiConverter) + .client(okHttpBuilder.build()) + .build() } fun createNetworkLoggingInterceptor(): Interceptor { 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 index 384676621a..f32626bf66 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt @@ -25,9 +25,10 @@ internal fun OkHttpClient.Builder.addHeaders(vararg requestHeaders: RequestHeade * * @param level logging level. By default, only the request body. */ -internal fun OkHttpClient.Builder.allowLogging(): OkHttpClient.Builder = - if (BuildConfig.DEBUG) { +internal fun OkHttpClient.Builder.allowLogging(): OkHttpClient.Builder { + return if (BuildConfig.DEBUG) { addInterceptor(interceptor = createNetworkLoggingInterceptor()) } else { this - } \ No newline at end of file + } +} diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 9b66d181ca..210582bd45 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -76,7 +76,7 @@ reactiveNetwork = "3.0.8" walletConnectCore = "1.15.0" walletConnectWeb3 = "1.8.0" prettyLogger = "2.2.0" -prettyOkHttpLoggingInterceptor = "3.1.0" +okHttp-prettyLogging = "3.1.0" # endregion Other libraries # region Tangem @@ -203,7 +203,7 @@ material = { module = "com.google.android.material:material", version.ref = "goo moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } -okHttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } +okHttp-prettyLogging = { module = "com.github.ihsanbal:LoggingInterceptor", version.ref = "okHttp-prettyLogging" } otaliastudiosCameraView = { module = "com.otaliastudios:cameraview", version.ref = "otaliastudiosCameraView" } shopify-buy = { module = "com.shopify.mobilebuysdk:buy3", version.ref = "shopifyBuySdk" } spongecastle-core = { module = "com.madgag.spongycastle:core", version.ref = "spongycastleCryptoCore" } @@ -225,5 +225,4 @@ reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", versio walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" } walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" } prettyLogger = { module = "com.orhanobut:logger", version.ref = "prettyLogger" } -prettyOkHttpLoggingInterceptor = { module = "com.github.ihsanbal:LoggingInterceptor", version.ref = "prettyOkHttpLoggingInterceptor" } # endregion Other From 5b0d90152ed22f281614c313ff2c09ec5b2c9931 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 7 Jul 2023 19:01:35 +0300 Subject: [PATCH 18/58] Updated on 2026-08-14 --- app/build.gradle.kts | 7 ------- domain/legacy/build.gradle.kts | 6 ------ 2 files changed, 13 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 14433e7634..507d70e3b1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -114,13 +114,6 @@ dependencies { implementation(deps.appsflyer) implementation(deps.amplitude) implementation(deps.kotsonGson) - //TODO: refactoring: remove it when all network services moved to the datasource module - implementation(deps.retrofit) - implementation(deps.retrofit.moshi) - implementation(deps.moshi) - implementation(deps.moshi.kotlin) - implementation(deps.okHttp) - implementation(deps.okHttp.logging) implementation(deps.zendesk.chat) implementation(deps.zendesk.messaging) implementation(deps.spongecastle.core) diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index f618e11fd9..320f10a255 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -22,12 +22,6 @@ dependencies { /** Other libraries */ implementation(deps.reKotlin) - //TODO: refactoring: remove it when all network services moved to the datasource module - implementation(deps.retrofit) - implementation(deps.retrofit.moshi) - implementation(deps.moshi) - implementation(deps.moshi.kotlin) - implementation(deps.okHttp.logging) implementation(deps.timber) implementation(deps.kotlin.coroutines) From 0c638c7c96bae786f1213e8552d4fea2c457ecff Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 10 Jul 2023 12:17:27 +0300 Subject: [PATCH 19/58] Updated on 2026-08-14 --- domain/legacy/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index 320f10a255..e9449ad7de 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -22,6 +22,8 @@ dependencies { /** Other libraries */ implementation(deps.reKotlin) + implementation(deps.moshi) + implementation(deps.moshi.kotlin) implementation(deps.timber) implementation(deps.kotlin.coroutines) From 86ca9ebbc98b6dfbb3ca7046785901f224295eb1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 10 Jul 2023 13:02:08 +0300 Subject: [PATCH 20/58] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/TapApplication.kt | 14 ++++++++------ .../com/tangem/datasource/utils/HttpClientExt.kt | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 4d7bc9bd30..9c3d7f67cb 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -174,11 +174,13 @@ class TapApplication : Application(), ImageLoaderFactory { ) if (BuildConfig.DEBUG) { - Timber.plant(object : Timber.DebugTree() { - override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { - Logger.log(priority, tag, message, t) - } - }) + Timber.plant( + object : Timber.DebugTree() { + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + Logger.log(priority, tag, message, t) + } + }, + ) } foregroundActivityObserver = ForegroundActivityObserver() @@ -197,7 +199,7 @@ class TapApplication : Application(), ImageLoaderFactory { if (LogConfig.network.blockchainSdkNetwork) { BlockchainSdkRetrofitBuilder.interceptors = listOf( - createNetworkLoggingInterceptor() + createNetworkLoggingInterceptor(), ) } 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 index f32626bf66..4a5a6a1b75 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/HttpClientExt.kt @@ -31,4 +31,4 @@ internal fun OkHttpClient.Builder.allowLogging(): OkHttpClient.Builder { } else { this } -} +} \ No newline at end of file From 9076665333f79b8feba2de90cb5058be2818130e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Jul 2023 12:49:48 +0300 Subject: [PATCH 21/58] Updated on 2026-08-14 --- .../com/tangem/domain/common/extensions/Blockchain.kt | 8 +------- gradle/dependencies.toml | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index 15fb408bde..f4084f38df 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -29,7 +29,6 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "bitcoin-cash/test" -> Blockchain.BitcoinCashTestnet "cardano" -> Blockchain.CardanoShelley "dogecoin" -> Blockchain.Dogecoin - "ducatus" -> Blockchain.Ducatus "litecoin" -> Blockchain.Litecoin "rootstock" -> Blockchain.RSK "stellar" -> Blockchain.Stellar @@ -87,7 +86,6 @@ fun Blockchain.toNetworkId(): String { Blockchain.Cardano -> "cardano" Blockchain.CardanoShelley -> "cardano" Blockchain.Dogecoin -> "dogecoin" - Blockchain.Ducatus -> "ducatus" Blockchain.Ethereum -> "ethereum" Blockchain.EthereumTestnet -> "ethereum/test" Blockchain.EthereumClassic -> "ethereum-classic" @@ -152,7 +150,6 @@ fun Blockchain.toCoinId(): String { Blockchain.Fantom, Blockchain.FantomTestnet -> "fantom" Blockchain.Tron, Blockchain.TronTestnet -> "tron" Blockchain.Polkadot, Blockchain.PolkadotTestnet -> "polkadot" - Blockchain.Ducatus -> "ducatus" Blockchain.Litecoin -> "litecoin" Blockchain.RSK -> "rootstock" Blockchain.Tezos -> "tezos" @@ -182,7 +179,4 @@ fun Blockchain.isSupportedInApp(): Boolean { return !excludedBlockchains.contains(this) } -private val excludedBlockchains = listOf( - Blockchain.Unknown, - Blockchain.Ducatus, -) \ No newline at end of file +private val excludedBlockchains = listOf(Blockchain.Unknown) \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 515c8832d2..042782007e 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -78,7 +78,7 @@ walletConnectWeb3 = "1.10.0" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-268" +tangemBlockchainSdk = "develop-264" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-266" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds From 5624193e7b8879b79fbef1530150813a5e1d6a05 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 10 Jul 2023 15:25:39 +0400 Subject: [PATCH 22/58] Updated on 2026-08-14 --- .../tap/domain/tasks/product/ResetToFactorySettingsTask.kt | 3 +-- .../src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt | 2 +- gradle/dependencies.toml | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) 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 1ab2b50b69..a2622b218d 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 @@ -31,8 +31,7 @@ class ResetToFactorySettingsTask : CardSessionRunnable { } private fun resetBackup(session: CardSession, callback: (result: CompletionResult) -> Unit) { - val backupStatus = session.environment.card?.backupStatus - if (backupStatus == null || backupStatus == Card.BackupStatus.NoBackup) { + if (session.environment.card?.backupStatus?.isActive != true) { callback(CompletionResult.Success(session.environment.card!!)) return } diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt index 7578791134..a89909b584 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt @@ -304,7 +304,7 @@ data class CardDTO( object NoBackup : BackupStatus() val isActive: Boolean - get() = this is Active || this is CardLinked + get() = this is Active companion object { internal fun fromSdkStatus(sdkStatus: Card.BackupStatus?): BackupStatus? { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 042782007e..23948fc2c6 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -80,7 +80,7 @@ walletConnectWeb3 = "1.10.0" # region Tangem tangemBlockchainSdk = "develop-264" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-266" +tangemCardSdk = "develop-273" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds # endregion Tangem From c698ca194cc85932f277c219b854a1b66aced24e Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Jul 2023 20:11:39 +0800 Subject: [PATCH 23/58] Updated on 2026-08-14 --- core/analytics/models/.gitignore | 1 + core/analytics/models/build.gradle.kts | 4 ++++ .../java/com/tangem/core/analytics/models}/AnalyticsEvent.kt | 2 +- settings.gradle.kts | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 core/analytics/models/.gitignore create mode 100644 core/analytics/models/build.gradle.kts rename core/analytics/{src/main/java/com/tangem/core/analytics => models/src/main/java/com/tangem/core/analytics/models}/AnalyticsEvent.kt (81%) diff --git a/core/analytics/models/.gitignore b/core/analytics/models/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/core/analytics/models/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/analytics/models/build.gradle.kts b/core/analytics/models/build.gradle.kts new file mode 100644 index 0000000000..7ff7fb7522 --- /dev/null +++ b/core/analytics/models/build.gradle.kts @@ -0,0 +1,4 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + id("configuration") +} \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/AnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt similarity index 81% rename from core/analytics/src/main/java/com/tangem/core/analytics/AnalyticsEvent.kt rename to core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt index 1fbc89d977..e60b14d1ee 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/AnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsEvent.kt @@ -1,4 +1,4 @@ -package com.tangem.core.analytics +package com.tangem.core.analytics.models /** [REDACTED_AUTHOR] diff --git a/settings.gradle.kts b/settings.gradle.kts index 523476db95..9a14394b73 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,6 +35,7 @@ include(":common") // region Core modules include(":core:analytics") +include(":core:analytics:models") include(":core:datasource") include(":core:featuretoggles") include(":core:navigation") From b5ad0f297270b749fcef5c745f24d0ebe5bcd715 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Jul 2023 20:19:14 +0800 Subject: [PATCH 24/58] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../tap/common/analytics/events/Basic.kt | 2 +- .../tap/common/analytics/events/Chat.kt | 2 +- .../common/analytics/events/DetailsScreen.kt | 2 +- .../analytics/events/IntroductionProcess.kt | 2 +- .../tap/common/analytics/events/MainScreen.kt | 3 +-- .../common/analytics/events/ManageTokens.kt | 2 +- .../tap/common/analytics/events/MyWallets.kt | 2 +- .../tap/common/analytics/events/Onboarding.kt | 20 +------------------ .../tap/common/analytics/events/Portfolio.kt | 2 +- .../tap/common/analytics/events/Settings.kt | 2 +- .../tap/common/analytics/events/Shop.kt | 2 +- .../tap/common/analytics/events/SignIn.kt | 2 +- .../tap/common/analytics/events/Token.kt | 2 +- .../common/analytics/events/WalletConnect.kt | 2 +- .../appsFlyer/AppsFlyerAnalyticsHandler.kt | 2 +- .../firebase/FirebaseAnalyticsHandler.kt | 2 +- .../CardContextInterceptor.kt | 2 +- .../LinkedCardContextInterceptor.kt | 4 ++-- .../domain/scanCard/LegacyScanProcessor.kt | 2 +- .../tap/domain/scanCard/ScanCardProcessor.kt | 2 +- .../domain/scanCard/UseCaseScanProcessor.kt | 2 +- .../domain/scanCard/chains/AnalyticsChain.kt | 2 +- .../tap/features/home/redux/HomeAction.kt | 2 +- .../tap/features/home/redux/HomeMiddleware.kt | 2 +- .../tap/features/wallet/redux/WalletAction.kt | 2 +- core/analytics/build.gradle.kts | 4 +++- .../com/tangem/core/analytics/Analytics.kt | 15 +++----------- .../core/analytics/api/EventFilterApi.kt | 2 +- .../core/analytics/api/EventHandlerApi.kt | 2 +- .../analytics/api/ParamsInterceptorApi.kt | 2 +- domain/card/build.gradle.kts | 6 ++++-- features/learn2earn/impl/build.gradle.kts | 1 + .../learn2earn/analytics/Learn2earnEvents.kt | 2 +- .../domain/api/WebViewResultHandler.kt | 2 +- features/onboarding/build.gradle.kts | 1 + .../wallet2/analytics/SeedPhraseEvents.kt | 2 +- .../referral/presentation/build.gradle.kts | 1 + .../referral/analytics/ReferralEvents.kt | 2 +- features/swap/presentation/build.gradle.kts | 1 + .../feature/swap/analytics/SwapEvents.kt | 4 +--- features/wallet/impl/build.gradle.kts | 3 +++ 42 files changed, 52 insertions(+), 70 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index df6d24b384..ca27f5fd67 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { implementation(project(":domain:wallets:models")) implementation(project(":common")) implementation(project(":core:analytics")) + implementation(projects.core.analytics.models) implementation(project(":core:navigation")) implementation(project(":core:featuretoggles")) implementation(project(":core:res")) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index e5b89b30e5..2782ceac8f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt index 96fa307105..f4b9dbc61f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Chat.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt index a0d34306c9..775f44a0d8 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/DetailsScreen.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt index 602365c0c6..b5ce0042ae 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/IntroductionProcess.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt index 7a36f35d1e..fbbc88c099 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MainScreen.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] @@ -13,7 +13,6 @@ sealed class MainScreen( class ScreenOpened : MainScreen("Screen opened") class ButtonScanCard : MainScreen("Button - Scan Card") class ButtonMyWallets : MainScreen("Button - My Wallets") - class ButtonBuy : MainScreen("Button - Buy") class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( event = "Enable Biometric", diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt index 8e0b8cc5b9..969d1d47fb 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/ManageTokens.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.domain.common.extensions.toNetworkId import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.tap.common.extensions.filterNotNull diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt index 65ab7937c2..87ba002047 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/MyWallets.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent sealed class MyWallets( event: String, diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt index 98dd408ba8..9612f2b972 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Onboarding.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] @@ -72,26 +72,8 @@ sealed class Onboarding( class SetupFinished : Twins("Twin Setup Finished") } - class PinScreenOpened : Onboarding("Onboarding", "PIN screen opened") - class ButtonSetPinCode : Onboarding("Onboarding", "Button - Set PIN Code") - class CardConnectionScreenOpened : Onboarding("Onboarding", "Card Connection Screen Opened") - class ButtonConnect : Onboarding("Onboarding", "Button - Connect") - class PinCodeSet : Onboarding("Onboarding", "PIN Code Set") - - class KYCScreenOpened : Onboarding("Onboarding", "KYC screen opened") - class KYCStarted : Onboarding("Onboarding", "KYC Started") - class KYCInProgress : Onboarding("Onboarding", "KYC In Progress") - class KYCRejected : Onboarding("Onboarding", "KYC Rejected") - - class ClaimScreenOpened : Onboarding("Onboarding", "Claim Screen Opened") - class ButtonClaim : Onboarding("Onboarding", "Button - Claim") - class ClaimWasSuccessfully : Onboarding("Onboarding", "Claim Was Successfully") - class ButtonChat : Onboarding("Onboarding", "Button - Chat") - class NotEnoughGasError : Onboarding("Onboarding", "Not Enough Gas Error") - class CardNotPassedError : Onboarding("Onboarding", "Card Not Passed Error") - class EnableBiometrics(state: AnalyticsParam.OnOffState) : Onboarding( category = "Onboarding / Biometric", event = "Enable Biometric", diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt index 71383c9f9a..3e961883b7 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Portfolio.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt index 5a9226bec4..e71eda15ad 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Settings.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.tap.features.details.ui.details.SocialNetwork /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt index 0ab4dd9361..4076de8685 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Shop.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.tap.common.extensions.filterNotNull /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt index a5417d775b..de174fd193 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/SignIn.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 837c9bce3c..9b8cecc417 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam.CurrencyType /** diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt index 34047928e4..49c49b912a 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/WalletConnect.kt @@ -1,6 +1,6 @@ package com.tangem.tap.common.analytics.events -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt index d1b3189ab1..c4b12fc278 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsFlyer/AppsFlyerAnalyticsHandler.kt @@ -1,8 +1,8 @@ package com.tangem.tap.common.analytics.handlers.appsFlyer import com.appsflyer.AFInAppEventType -import com.tangem.core.analytics.AnalyticsEvent import com.tangem.core.analytics.api.AnalyticsHandler +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.events.Shop diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt index 7430fcc17f..e445597e8f 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/firebase/FirebaseAnalyticsHandler.kt @@ -1,9 +1,9 @@ package com.tangem.tap.common.analytics.handlers.firebase import com.google.firebase.analytics.FirebaseAnalytics -import com.tangem.core.analytics.AnalyticsEvent import com.tangem.core.analytics.api.AnalyticsHandler import com.tangem.core.analytics.api.ErrorEventHandler +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder import com.tangem.tap.common.analytics.converters.AnalyticsErrorConverter import com.tangem.tap.common.analytics.events.Shop diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt index 864c9dadbb..0db51098a3 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/CardContextInterceptor.kt @@ -1,7 +1,7 @@ package com.tangem.tap.common.analytics.paramsInterceptor -import com.tangem.core.analytics.AnalyticsEvent import com.tangem.core.analytics.api.ParamsInterceptor +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse diff --git a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/LinkedCardContextInterceptor.kt b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/LinkedCardContextInterceptor.kt index 94c006d699..25b67c3ee0 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/LinkedCardContextInterceptor.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/paramsInterceptor/LinkedCardContextInterceptor.kt @@ -1,14 +1,14 @@ package com.tangem.tap.common.analytics.paramsInterceptor -import com.tangem.core.analytics.AnalyticsEvent import com.tangem.core.analytics.api.ParamsInterceptor +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.domain.models.scan.ScanResponse /** [REDACTED_AUTHOR] */ class LinkedCardContextInterceptor( - private val scanResponse: ScanResponse, + scanResponse: ScanResponse, val parent: LinkedCardContextInterceptor? = null, ) : ParamsInterceptor { diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index e83d2496ff..e891d0dd2c 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -6,7 +6,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.extensions.withMainContext 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 0546a79db5..808f338f85 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 @@ -2,7 +2,7 @@ package com.tangem.tap.domain.scanCard import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt index ae4e286e5f..002b9bc7ff 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/UseCaseScanProcessor.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard import arrow.fx.coroutines.resourceScope import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.card.ScanCardException diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/AnalyticsChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/AnalyticsChain.kt index febc6a1525..8f72ce92f8 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/AnalyticsChain.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/AnalyticsChain.kt @@ -3,7 +3,7 @@ package com.tangem.tap.domain.scanCard.chains import arrow.core.Either import arrow.core.right import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.domain.card.ScanCardException import com.tangem.domain.core.chain.Chain import com.tangem.domain.models.scan.ScanResponse diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index 86b139defb..93479d1767 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -1,6 +1,6 @@ package com.tangem.tap.features.home.redux -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.entities.IndeterminateProgressButton 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 7a77fcaa60..8a225afa8a 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 @@ -5,7 +5,7 @@ import com.tangem.common.doOnResult import com.tangem.common.doOnSuccess import com.tangem.common.extensions.guard import com.tangem.core.analytics.Analytics -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.models.scan.ScanResponse 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 3f73a94c11..10803cf57f 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 @@ -3,7 +3,7 @@ package com.tangem.tap.features.wallet.redux import android.content.Context import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.address.AddressType -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.domain.common.BlockchainNetwork import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.wallets.models.UserWallet diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts index e1fb463a41..29b4cfb591 100644 --- a/core/analytics/build.gradle.kts +++ b/core/analytics/build.gradle.kts @@ -13,5 +13,7 @@ dependencies { kapt(deps.hilt.kapt) /** Core shouldn't depends on core, but in case with utils and logging its necessary */ - implementation(project(":core:utils")) + implementation(projects.core.utils) + + implementation(projects.core.analytics.models) } \ No newline at end of file diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt index 8dda5adedf..3c12cf80a8 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/Analytics.kt @@ -1,18 +1,9 @@ package com.tangem.core.analytics -import com.tangem.core.analytics.api.AnalyticsEventFilter -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.api.AnalyticsFilterHolder -import com.tangem.core.analytics.api.AnalyticsHandler -import com.tangem.core.analytics.api.AnalyticsHandlerHolder -import com.tangem.core.analytics.api.ParamsInterceptor -import com.tangem.core.analytics.api.ParamsInterceptorHolder +import com.tangem.core.analytics.api.* +import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import java.util.concurrent.Executors /** diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt index 8381b2b983..4de7e4aee8 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventFilterApi.kt @@ -1,6 +1,6 @@ package com.tangem.core.analytics.api -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt index 49aadf88c3..0a5f3179de 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/EventHandlerApi.kt @@ -1,6 +1,6 @@ package com.tangem.core.analytics.api -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt b/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt index 683a1c45a0..80b0bf2b14 100644 --- a/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt +++ b/core/analytics/src/main/java/com/tangem/core/analytics/api/ParamsInterceptorApi.kt @@ -1,6 +1,6 @@ package com.tangem.core.analytics.api -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** [REDACTED_AUTHOR] diff --git a/domain/card/build.gradle.kts b/domain/card/build.gradle.kts index 213bd024b6..a1266a8ba6 100644 --- a/domain/card/build.gradle.kts +++ b/domain/card/build.gradle.kts @@ -4,9 +4,11 @@ plugins { } dependencies { - implementation(project(":domain:core")) + implementation(projects.domain.core) // TODO: Remove after new card scan result was implemented - implementation(project(":domain:models")) + implementation(projects.domain.models) + + implementation(projects.core.analytics.models) implementation(deps.tangem.card.core) } \ No newline at end of file diff --git a/features/learn2earn/impl/build.gradle.kts b/features/learn2earn/impl/build.gradle.kts index 26d0179187..37925318a5 100644 --- a/features/learn2earn/impl/build.gradle.kts +++ b/features/learn2earn/impl/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { /** Core modules */ implementation(project(":common")) implementation(project(":core:analytics")) + implementation(projects.core.analytics.models) implementation(project(":core:featuretoggles")) implementation(project(":core:datasource")) implementation(project(":core:utils")) diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/analytics/Learn2earnEvents.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/analytics/Learn2earnEvents.kt index 164fa4e793..100a19ef0a 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/analytics/Learn2earnEvents.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/analytics/Learn2earnEvents.kt @@ -1,6 +1,6 @@ package com.tangem.feature.learn2earn.analytics -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent internal sealed class Learn2earnEvents( category: String, diff --git a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt index 6b7c851a69..e9cc14f222 100644 --- a/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt +++ b/features/learn2earn/impl/src/main/java/com/tangem/feature/learn2earn/domain/api/WebViewResultHandler.kt @@ -1,6 +1,6 @@ package com.tangem.feature.learn2earn.domain.api -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent /** * Handler that helps determine a result of the Learn2earnWebViewActivity webView actions. diff --git a/features/onboarding/build.gradle.kts b/features/onboarding/build.gradle.kts index 1940a2073c..cdd735f59c 100644 --- a/features/onboarding/build.gradle.kts +++ b/features/onboarding/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(project(":core:featuretoggles")) implementation(project(":core:datasource")) implementation(project(":core:analytics")) + implementation(projects.core.analytics.models) implementation(project(":core:utils")) implementation(project(":core:ui")) implementation(project(":core:res")) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/analytics/SeedPhraseEvents.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/analytics/SeedPhraseEvents.kt index baf6d039cc..d593c179ed 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/analytics/SeedPhraseEvents.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/analytics/SeedPhraseEvents.kt @@ -1,6 +1,6 @@ package com.tangem.feature.onboarding.presentation.wallet2.analytics -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent sealed class SeedPhraseEvents( event: String, diff --git a/features/referral/presentation/build.gradle.kts b/features/referral/presentation/build.gradle.kts index 1f66fc2708..0649e10587 100644 --- a/features/referral/presentation/build.gradle.kts +++ b/features/referral/presentation/build.gradle.kts @@ -9,6 +9,7 @@ plugins { dependencies { /** Core modules */ implementation(project(":core:analytics")) + implementation(projects.core.analytics.models) implementation(project(":core:res")) implementation(project(":core:utils")) implementation(project(":core:ui")) diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/analytics/ReferralEvents.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/analytics/ReferralEvents.kt index e82990adf2..a5d37a1b19 100644 --- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/analytics/ReferralEvents.kt +++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/analytics/ReferralEvents.kt @@ -1,6 +1,6 @@ package com.tangem.feature.referral.analytics -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent sealed class ReferralEvents(event: String) : AnalyticsEvent(REFERRAL_PROGRAM_CATEGORY, event) { diff --git a/features/swap/presentation/build.gradle.kts b/features/swap/presentation/build.gradle.kts index f4a0857938..8248b21113 100644 --- a/features/swap/presentation/build.gradle.kts +++ b/features/swap/presentation/build.gradle.kts @@ -10,6 +10,7 @@ plugins { dependencies { /** Core modules */ implementation(project(":core:analytics")) + implementation(projects.core.analytics.models) implementation(project(":core:featuretoggles")) implementation(project(":core:utils")) implementation(project(":core:ui")) diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt index e808db167c..d2395cf29b 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt @@ -1,6 +1,6 @@ package com.tangem.feature.swap.analytics -import com.tangem.core.analytics.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsEvent sealed class SwapEvents( event: String, @@ -13,7 +13,6 @@ sealed class SwapEvents( ) object SendTokenBalanceClicked : SwapEvents(event = "Send Token Balance Clicked") - object ReceiveTokenClicked : SwapEvents(event = "Receive Token Clicked") object ChooseTokenScreenOpened : SwapEvents(event = "Choose Token Screen Opened") object SearchTokenClicked : SwapEvents(event = "Searched Token Clicked") data class ButtonSwapClicked(val sendToken: String, val receiveToken: String) : SwapEvents( @@ -24,7 +23,6 @@ sealed class SwapEvents( object ButtonGivePermissionClicked : SwapEvents(event = "Button - Give permission") object ButtonPermissionApproveClicked : SwapEvents(event = "Button - Permission Approve") object ButtonPermissionCancelClicked : SwapEvents(event = "Button - Permission Cancel") - object ButtonPermitAndSwapClicked : SwapEvents(event = "Button - Permit and Swap") object ButtonSwipeClicked : SwapEvents(event = "Button - Swipe") object SwapInProgressScreen : SwapEvents(event = "Swap in Progress Screen Opened") } diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 2e5651a89c..64139b1ecb 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { /** Other libraries */ implementation(deps.kotlin.immutable.collections) + implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) /** DI */ @@ -37,12 +38,14 @@ dependencies { implementation(project(":core:featuretoggles")) implementation(project(":core:navigation")) implementation(project(":core:ui")) + implementation(projects.core.utils) /** Feature Apis */ implementation(project(":features:wallet:api")) /** Domain modules */ implementation(project(":common")) + implementation(projects.domain.card) implementation(project(":domain:legacy")) implementation(project(":domain:models")) implementation(project(":domain:wallets")) From 80ac1d02c03e7d39ce01ccf3ddc90a74ec997dbb Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Jul 2023 09:52:20 +0300 Subject: [PATCH 25/58] Updated on 2026-08-14 --- app/build.gradle.kts | 2 + .../tap/di/domain/TokensDomainModule.kt | 26 ++++++++++++ data/tokens/.gitignore | 1 + data/tokens/build.gradle.kts | 17 ++++++++ .../tangem/data/tokens/di/TokensDataModule.kt | 30 +++++++++++++ .../repository/DefaultNetworksRepository.kt | 25 +++++++++++ .../repository/DefaultQuotesRepository.kt | 15 +++++++ .../repository/DefaultTokensRepository.kt | 32 ++++++++++++++ domain/tokens/.gitignore | 1 + domain/tokens/build.gradle.kts | 10 +++++ .../domain/tokens/GetTokenListUseCase.kt | 28 +++++++++++++ .../tangem/domain/tokens/error/TokensError.kt | 6 +++ .../com/tangem/domain/tokens/model/Network.kt | 10 +++++ .../domain/tokens/model/NetworkGroup.kt | 9 ++++ .../domain/tokens/model/NetworkStatus.kt | 23 ++++++++++ .../com/tangem/domain/tokens/model/Quote.kt | 9 ++++ .../com/tangem/domain/tokens/model/Token.kt | 14 +++++++ .../tangem/domain/tokens/model/TokenList.kt | 38 +++++++++++++++++ .../tangem/domain/tokens/model/TokenStatus.kt | 42 +++++++++++++++++++ .../tokens/repository/NetworksRepository.kt | 26 ++++++++++++ .../tokens/repository/QuotesRepository.kt | 18 ++++++++ .../tokens/repository/TokensRepository.kt | 29 +++++++++++++ features/wallet/impl/build.gradle.kts | 1 + .../router/DefaultWalletRouter.kt | 3 +- .../wallet/viewmodels/WalletViewModel.kt | 6 ++- settings.gradle.kts | 2 + 26 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt create mode 100644 data/tokens/.gitignore create mode 100644 data/tokens/build.gradle.kts create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensRepository.kt create mode 100644 domain/tokens/.gitignore create mode 100644 domain/tokens/build.gradle.kts create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Network.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkStatus.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Quote.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Token.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenStatus.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b56b814c08..07f7cd8b81 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(project(":domain:card")) implementation(project(":domain:wallets")) implementation(project(":domain:wallets:models")) + implementation(projects.domain.tokens) implementation(project(":common")) implementation(project(":core:analytics")) implementation(projects.core.analytics.models) @@ -40,6 +41,7 @@ dependencies { implementation(project(":libs:crypto")) implementation(project(":libs:auth")) implementation(project(":data:source:preferences")) + implementation(projects.data.tokens) /** Features */ implementation(project(":features:onboarding")) diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt new file mode 100644 index 0000000000..bda1a7734d --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -0,0 +1,26 @@ +package com.tangem.tap.di.domain + +import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.tokens.repository.TokensRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped + +@Module +@InstallIn(ViewModelComponent::class) +internal object TokensDomainModule { + + @Provides + @ViewModelScoped + fun provideGetTokenListUseCase( + tokensRepository: TokensRepository, + quotesRepository: QuotesRepository, + networksRepository: NetworksRepository, + dispatchers: CoroutineDispatcherProvider, + ) = GetTokenListUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers) +} \ No newline at end of file diff --git a/data/tokens/.gitignore b/data/tokens/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/data/tokens/.gitignore @@ -0,0 +1 @@ +/build diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts new file mode 100644 index 0000000000..95abf3ed40 --- /dev/null +++ b/data/tokens/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + alias(deps.plugins.kotlin.kapt) + id("configuration") +} + +dependencies { + implementation(projects.domain.tokens) + implementation(projects.domain.wallets.models) + + implementation(deps.kotlin.coroutines) + + implementation(deps.arrow.core) + + implementation(deps.hilt.core) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt new file mode 100644 index 0000000000..9f88733061 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -0,0 +1,30 @@ +package com.tangem.data.tokens.di + +import com.tangem.data.tokens.repository.DefaultNetworksRepository +import com.tangem.data.tokens.repository.DefaultQuotesRepository +import com.tangem.data.tokens.repository.DefaultTokensRepository +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.tokens.repository.TokensRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object TokensDataModule { + + @Provides + @Singleton + fun provideTokensRepository(): TokensRepository = DefaultTokensRepository() + + @Provides + @Singleton + fun provideQuotesRepository(): QuotesRepository = DefaultQuotesRepository() + + @Provides + @Singleton + fun provideNetworksRepository(): NetworksRepository = DefaultNetworksRepository() +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt new file mode 100644 index 0000000000..dbad76f32b --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -0,0 +1,25 @@ +package com.tangem.data.tokens.repository + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +internal class DefaultNetworksRepository : NetworksRepository { + + override fun getNetworks(networksIds: Set): Either> { + TODO("Not yet implemented") + } + + override fun getNetworkStatuses( + userWalletId: UserWalletId, + networks: Map>, + refresh: Boolean, + ): Flow>> { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt new file mode 100644 index 0000000000..087f265e96 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt @@ -0,0 +1,15 @@ +package com.tangem.data.tokens.repository + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Quote +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.tokens.repository.QuotesRepository +import kotlinx.coroutines.flow.Flow + +internal class DefaultQuotesRepository : QuotesRepository { + + override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow>> { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensRepository.kt new file mode 100644 index 0000000000..cb7575e7fa --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensRepository.kt @@ -0,0 +1,32 @@ +package com.tangem.data.tokens.repository + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.tokens.repository.TokensRepository +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +internal class DefaultTokensRepository : TokensRepository { + + override suspend fun sortTokens( + userWalletId: UserWalletId, + sortedTokensIds: Set, + isGrouped: Boolean, + isSortedByBalance: Boolean, + ): Either { + TODO("Not yet implemented") + } + + override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> { + TODO("Not yet implemented") + } + + override fun isTokensGrouped(userWalletId: UserWalletId): Flow> { + TODO("Not yet implemented") + } + + override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/domain/tokens/.gitignore b/domain/tokens/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/domain/tokens/.gitignore @@ -0,0 +1 @@ +/build diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts new file mode 100644 index 0000000000..d4297e1590 --- /dev/null +++ b/domain/tokens/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + alias(deps.plugins.kotlin.jvm) + id("configuration") +} + +dependencies { + implementation(projects.domain.core) + implementation(projects.domain.wallets.models) + implementation(projects.core.utils) +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt new file mode 100644 index 0000000000..0f52a195f9 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -0,0 +1,28 @@ +// TODO: [REDACTED_JIRA] +@file:Suppress("unused", "unused_parameter") + +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.left +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.tokens.repository.TokensRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +class GetTokenListUseCase( + private val tokensRepository: TokensRepository, + private val quotesRepository: QuotesRepository, + private val networksRepository: NetworksRepository, + private val dispatchers: CoroutineDispatcherProvider, +) { + + operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow> { + return flowOf(TokensError.EmptyTokens.left()) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt new file mode 100644 index 0000000000..4647779219 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.tokens.error + +sealed class TokensError { + + object EmptyTokens : TokensError() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Network.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Network.kt new file mode 100644 index 0000000000..b4b7628aa4 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Network.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.tokens.model + +data class Network( + val id: ID, + val name: String, +) { + + @JvmInline + value class ID(val value: String) +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt new file mode 100644 index 0000000000..ea33623175 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.tokens.model + +import arrow.core.NonEmptySet + +data class NetworkGroup( + val networkId: Network.ID, + val name: String, + val tokens: NonEmptySet, +) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkStatus.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkStatus.kt new file mode 100644 index 0000000000..36d1bc30e6 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkStatus.kt @@ -0,0 +1,23 @@ +package com.tangem.domain.tokens.model + +import java.math.BigDecimal + +data class NetworkStatus( + val networkId: Network.ID, + val value: Status, +) { + + sealed class Status { + open val amounts: Map? = null + } + + object Unreachable : Status() + + object MissedDerivation : Status() + + data class TransactionInProgress(override val amounts: Map) : Status() + + data class Verified(override val amounts: Map) : Status() + + data class NoAccount(val amountToCreateAccount: BigDecimal) : Status() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Quote.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Quote.kt new file mode 100644 index 0000000000..fad3db3666 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Quote.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.tokens.model + +import java.math.BigDecimal + +data class Quote( + val tokenId: Token.ID, + val fiatRate: BigDecimal, + val priceChange: BigDecimal, +) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Token.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Token.kt new file mode 100644 index 0000000000..cbaa05d20e --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Token.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.tokens.model + +data class Token( + val id: ID, + val networkId: Network.ID, + val name: String, + val symbol: String, + val isCustom: Boolean, + val isCoin: Boolean, +) { + + @JvmInline + value class ID(val value: String) +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt new file mode 100644 index 0000000000..e94a89de44 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt @@ -0,0 +1,38 @@ +package com.tangem.domain.tokens.model + +import arrow.core.NonEmptySet +import java.math.BigDecimal + +sealed class TokenList { + open val totalFiatBalance: FiatBalance = FiatBalance.Loading + open val sortedBy: SortType = SortType.NONE + + data class GroupedByNetwork( + val groups: NonEmptySet, + override val totalFiatBalance: FiatBalance, + override val sortedBy: SortType, + ) : TokenList() + + data class Ungrouped( + val tokens: NonEmptySet, + override val totalFiatBalance: FiatBalance, + override val sortedBy: SortType, + ) : TokenList() + + object NotInitialized : TokenList() + + enum class SortType { + NONE, BALANCE, + } + + sealed class FiatBalance { + object Loading : FiatBalance() + + object Failed : FiatBalance() + + data class Loaded( + val amount: BigDecimal, + val isAllAmountsSummarized: Boolean, + ) : FiatBalance() + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenStatus.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenStatus.kt new file mode 100644 index 0000000000..153caf20ba --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenStatus.kt @@ -0,0 +1,42 @@ +package com.tangem.domain.tokens.model + +import java.math.BigDecimal + +data class TokenStatus( + val id: Token.ID, + val networkId: Network.ID, + val name: String, + val symbol: String, + val isCoin: Boolean, + val value: Status, +) { + + sealed class Status { + open val amount: BigDecimal? = null + open val fiatAmount: BigDecimal? = null + open val priceChange: BigDecimal? = null + open val hasTransactionsInProgress: Boolean = false + } + + object Loading : Status() + + object Unreachable : Status() + + object MissedDerivation : Status() + + object NoAccount : Status() + + data class Loaded( + override val amount: BigDecimal, + override val fiatAmount: BigDecimal, + override val priceChange: BigDecimal, + override val hasTransactionsInProgress: Boolean, + ) : Status() + + data class Custom( + override val amount: BigDecimal, + override val fiatAmount: BigDecimal?, + override val priceChange: BigDecimal?, + override val hasTransactionsInProgress: Boolean, + ) : Status() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt new file mode 100644 index 0000000000..df231dd283 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -0,0 +1,26 @@ +package com.tangem.domain.tokens.repository + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +// FIXME: Use Raise as context instead of Effect when context receivers become stable +// [REDACTED_JIRA] + +/** + * Repository for everything related to the blockchain networks + * */ +interface NetworksRepository { + + fun getNetworks(networksIds: Set): Either> + + fun getNetworkStatuses( + userWalletId: UserWalletId, + networks: Map>, + refresh: Boolean, + ): Flow>> +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt new file mode 100644 index 0000000000..314c1d1177 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.tokens.repository + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Quote +import com.tangem.domain.tokens.model.Token +import kotlinx.coroutines.flow.Flow + +// FIXME: Use Raise as context instead of Effect when context receivers become stable +// [REDACTED_JIRA] + +/** + * Repository for everything related to the quotes of tokens + * */ +interface QuotesRepository { + + fun getQuotes(tokensIds: Set, refresh: Boolean): Flow>> +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt new file mode 100644 index 0000000000..4b1ed804c3 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt @@ -0,0 +1,29 @@ +package com.tangem.domain.tokens.repository + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +// FIXME: Use Raise as context instead of Effect when context receivers become stable +// [REDACTED_JIRA] + +/** + * Repository for everything related to the tokens of user wallet + * */ +interface TokensRepository { + + suspend fun sortTokens( + userWalletId: UserWalletId, + sortedTokensIds: Set, + isGrouped: Boolean, + isSortedByBalance: Boolean, + ): Either + + fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> + + fun isTokensGrouped(userWalletId: UserWalletId): Flow> + + fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> +} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 64139b1ecb..c0f88c2b7e 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -50,4 +50,5 @@ dependencies { implementation(project(":domain:models")) implementation(project(":domain:wallets")) implementation(project(":domain:wallets:models")) + implementation(projects.domain.tokens) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 12a164c4fa..2406ada436 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -40,7 +40,8 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation startDestination = WalletScreens.WALLET.name, ) { composable(WalletScreens.WALLET.name) { - val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } + val viewModel = hiltViewModel() + .apply { router = this@DefaultWalletRouter } WalletScreen(state = viewModel.uiState) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 4cdda4fe30..9c80b75b31 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder @@ -19,7 +20,10 @@ import kotlin.properties.Delegates [REDACTED_AUTHOR] */ @HiltViewModel -internal class WalletViewModel @Inject constructor() : ViewModel() { +internal class WalletViewModel @Inject constructor( + @Suppress("unused") // TODO: [REDACTED_JIRA] + private val getTokenListUseCase: GetTokenListUseCase, +) : ViewModel() { /** Feature router */ var router: InnerWalletRouter by Delegates.notNull() diff --git a/settings.gradle.kts b/settings.gradle.kts index 9a14394b73..423368f3d8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -81,8 +81,10 @@ include(":domain:core") include(":domain:card") include(":domain:wallets") include(":domain:wallets:models") +include(":domain:tokens") // endregion Domain modules // region Data modules +include(":data:tokens") include(":data:source:preferences") // endregion Data modules \ No newline at end of file From 6d1a15724f33cd4c7480e82e78097d458bb5f320 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Jul 2023 11:46:29 +0300 Subject: [PATCH 26/58] Updated on 2026-08-14 --- buildSrc/build/tmp/jar/MANIFEST.MF | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 buildSrc/build/tmp/jar/MANIFEST.MF diff --git a/buildSrc/build/tmp/jar/MANIFEST.MF b/buildSrc/build/tmp/jar/MANIFEST.MF deleted file mode 100644 index 58630c02ef..0000000000 --- a/buildSrc/build/tmp/jar/MANIFEST.MF +++ /dev/null @@ -1,2 +0,0 @@ -Manifest-Version: 1.0 - From 26f5a1da8239f4ab80759ffeb458bd410045f6d5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Jul 2023 13:11:05 +0300 Subject: [PATCH 27/58] Updated on 2026-08-14 --- .../walletconnect/WalletConnectSdkHelper.kt | 1 + .../wallet/ui/MultipleAddressUiHelper.kt | 17 +++++++---------- .../assets/configs/feature_toggles_config.json | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) 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 36314a2e25..b1a6774b38 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 @@ -77,6 +77,7 @@ class WalletConnectSdkHelper { val transactionData = TransactionData( amount = Amount(value, wallet.blockchain), + // TODO refactoring fee = Fee.Common(Amount(fee, wallet.blockchain)), sourceAddress = transaction.from, destinationAddress = transaction.to!!, 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 7caaabe6e1..aa62852b75 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 @@ -8,17 +8,14 @@ import com.tangem.wallet.R object MultipleAddressUiHelper { fun typeToId(type: AddressType, blockchain: Blockchain): Int { - return when (blockchain) { - in blockchainsSupportingSplit -> { - if (type == AddressType.Legacy) { - R.id.chip_legacy - } else { - R.id.chip_default - } - } - else -> { - View.NO_ID + return if (blockchain in blockchainsSupportingSplit) { + if (type == AddressType.Legacy) { + R.id.chip_legacy + } else { + R.id.chip_default } + } else { + View.NO_ID } } diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index ddc2033b7b..5dc4db710d 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -1,7 +1,7 @@ [ { "name": "OPTIMISM_SWAP_FEATURE_ENABLED", - "version": "1.0.0" + "version": "4.3.1" }, { "name": "REDESIGNED_CUSTOM_TOKEN_SCREEN_ENABLED", From ffc38eb02de4d50f427d4cce9561d9ffd4f2fc58 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Jul 2023 22:21:20 +0800 Subject: [PATCH 28/58] Updated on 2026-08-14 --- .../java/com/tangem/tap/TapApplication.kt | 5 +++ .../tangem/tap/di/domain/CardDomainModule.kt | 18 ++++++++++ ...ocessor.kt => DefaultScanCardProcessor.kt} | 27 ++++++++------- .../details/redux/DetailsMiddleware.kt | 5 +-- .../tap/features/home/redux/HomeMiddleware.kt | 4 +-- .../middlewares/MultiWalletMiddleware.kt | 5 +-- .../redux/WalletSelectorMiddleware.kt | 6 ++-- .../welcome/redux/WelcomeMiddleware.kt | 4 +-- .../tap/proxy/redux/DaggerGraphState.kt | 2 ++ .../tangem/domain/card/ScanCardProcessor.kt | 25 ++++++++++++++ .../wallet/viewmodels/WalletViewModel.kt | 34 ++++++++++++++++--- 11 files changed, 106 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt rename app/src/main/java/com/tangem/tap/domain/scanCard/{ScanCardProcessor.kt => DefaultScanCardProcessor.kt} (76%) create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index 9c3d7f67cb..b1c5862c9b 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -22,6 +22,7 @@ import com.tangem.datasource.config.FeaturesLocalLoader import com.tangem.datasource.config.models.Config import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.DomainLayer +import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.common.LogConfig import com.tangem.domain.wallets.legacy.WalletManagersRepository import com.tangem.feature.learn2earn.domain.api.Learn2earnInteractor @@ -152,6 +153,9 @@ class TapApplication : Application(), ImageLoaderFactory { @Inject lateinit var tokenDetailsFeatureToggles: TokenDetailsFeatureToggles + @Inject + lateinit var scanCardProcessor: ScanCardProcessor + override fun onCreate() { super.onCreate() @@ -169,6 +173,7 @@ class TapApplication : Application(), ImageLoaderFactory { walletConnectRepository = walletConnect2Repository, walletConnectSessionsRepository = walletConnectSessionsRepository, tokenDetailsFeatureToggles = tokenDetailsFeatureToggles, + scanCardProcessor = scanCardProcessor, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt new file mode 100644 index 0000000000..27d0e3e8df --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.di.domain + +import com.tangem.domain.card.ScanCardProcessor +import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object CardDomainModule { + + @Provides + @Singleton + fun provideScanCardUseCase(): ScanCardProcessor = DefaultScanCardProcessor() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt similarity index 76% rename from app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt rename to app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt index 808f338f85..7c21a8a2f2 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt @@ -3,20 +3,21 @@ package com.tangem.tap.domain.scanCard import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store // TODO: Remove this object after feature toggle was removed and use ScanCardUseCase instead -internal object ScanCardProcessor { +internal class DefaultScanCardProcessor : ScanCardProcessor { private val isNewCardScanningEnabled: Boolean get() = store.state.daggerGraphState .get(DaggerGraphState::customTokenFeatureToggles) .isNewCardScanningEnabled - suspend fun scan( - cardId: String? = null, - allowsRequestAccessCodeFromRepository: Boolean = false, + override suspend fun scan( + cardId: String?, + allowsRequestAccessCodeFromRepository: Boolean, ): CompletionResult { return if (isNewCardScanningEnabled) { UseCaseScanProcessor.scan(cardId, allowsRequestAccessCodeFromRepository) @@ -26,15 +27,15 @@ internal object ScanCardProcessor { } @Suppress("LongParameterList") - suspend fun scan( - analyticsEvent: AnalyticsEvent? = null, - cardId: String? = null, - onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, - onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {}, - onWalletNotCreated: suspend () -> Unit = {}, - disclaimerWillShow: () -> Unit = {}, - onFailure: suspend (error: TangemError) -> Unit = {}, - onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {}, + override suspend fun scan( + analyticsEvent: AnalyticsEvent?, + cardId: String?, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit, + onWalletNotCreated: suspend () -> Unit, + disclaimerWillShow: () -> Unit, + onFailure: suspend (error: TangemError) -> Unit, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit, ) { if (isNewCardScanningEnabled) { UseCaseScanProcessor.scan( 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 a992489911..b93e243322 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 @@ -25,7 +25,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder -import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation import com.tangem.tap.domain.userWalletList.isLockedSync @@ -33,6 +32,7 @@ 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 import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.tangemSdkManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers @@ -74,7 +74,8 @@ class DetailsMiddleware { is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action) DetailsAction.ScanCard -> { scope.launch { - ScanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true) + store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor) + .scan(allowsRequestAccessCodeFromRepository = true) .doOnSuccess { scanResponse -> // if we use biometric, scanResponse in GlobalState is null, and crashes NPE on twin cards store.dispatch(GlobalAction.SaveScanResponse(scanResponse)) 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 8a225afa8a..01e2d50c0d 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 @@ -21,12 +21,12 @@ import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction 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.BUY_WALLET_URL import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.signin.redux.SignInAction +import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Action @@ -78,7 +78,7 @@ private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) - ScanCardProcessor.scan( + store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan( analyticsEvent = analyticsEvent, onProgressStateChange = { showProgress -> if (showProgress) { 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 d008d1b2e0..3f080d645a 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 @@ -15,10 +15,10 @@ import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.redux.WalletState import com.tangem.tap.features.wallet.redux.models.WalletDialog +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.userWalletsListManager @@ -102,7 +102,8 @@ class MultiWalletMiddleware { } private fun scanAndUpdateCard(selectedUserWallet: UserWallet) = scope.launch(Dispatchers.Default) { - ScanCardProcessor.scan(selectedUserWallet.cardId, allowsRequestAccessCodeFromRepository = true) + store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor) + .scan(cardId = selectedUserWallet.cardId, allowsRequestAccessCodeFromRepository = true) .flatMap { scanResponse -> userWalletsListManager.update( userWalletId = selectedUserWallet.walletId, 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 00d1e687bb..148e95d323 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 @@ -20,8 +20,8 @@ import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.WalletStoreModel import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.model.builders.UserWalletIdBuilder -import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable +import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull @@ -133,7 +133,7 @@ internal class WalletSelectorMiddleware { useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) - ScanCardProcessor.scan( + store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan( analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.MyWallets), onWalletNotCreated = { // No need to rollback policy, continue with the policy set before the card scan @@ -208,7 +208,7 @@ internal class WalletSelectorMiddleware { private suspend fun unlockUserWalletWithScannedCard(userWallet: UserWallet): CompletionResult { Analytics.send(MyWallets.Button.WalletUnlockTapped()) tangemSdkManager.changeDisplayedCardIdNumbersCount(userWallet.scanResponse) - return ScanCardProcessor.scan() + return store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan() .map { scanResponse -> val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build() if (scannedUserWalletId == userWallet.walletId) { 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 8a96417455..7fab7c2a4f 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 @@ -16,10 +16,10 @@ import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.model.builders.UserWalletBuilder -import com.tangem.tap.domain.scanCard.ScanCardProcessor import com.tangem.tap.domain.userWalletList.unlockIfLockable import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler import com.tangem.tap.features.signin.redux.SignInAction +import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -103,7 +103,7 @@ internal class WelcomeMiddleware { tangemSdkManager.setAccessCodeRequestPolicy( useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, ) - ScanCardProcessor.scan( + store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan( analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn), onSuccess = { scanResponse -> scope.launch { onCardScanned(scanResponse) } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 84409d4e36..7bdabd80a3 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -2,6 +2,7 @@ package com.tangem.tap.proxy.redux import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.ScanCardUseCase import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles @@ -27,6 +28,7 @@ data class DaggerGraphState( val walletConnectInteractor: WalletConnectInteractor? = null, val tokenDetailsFeatureToggles: TokenDetailsFeatureToggles? = null, val tokenDetailsRouter: TokenDetailsRouter? = null, + val scanCardProcessor: ScanCardProcessor? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt new file mode 100644 index 0000000000..961d9acd07 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt @@ -0,0 +1,25 @@ +package com.tangem.domain.card + +import com.tangem.common.CompletionResult +import com.tangem.common.core.TangemError +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.domain.models.scan.ScanResponse + +interface ScanCardProcessor { + + suspend fun scan( + cardId: String? = null, + allowsRequestAccessCodeFromRepository: Boolean = false, + ): CompletionResult + + suspend fun scan( + analyticsEvent: AnalyticsEvent? = null, + cardId: String? = null, + onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, + onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {}, + onWalletNotCreated: suspend () -> Unit = {}, + disclaimerWillShow: () -> Unit = {}, + onFailure: suspend (error: TangemError) -> Unit = {}, + onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {}, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 9c80b75b31..2a2f30dcbb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -5,12 +5,18 @@ 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.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.properties.Delegates @@ -23,6 +29,8 @@ import kotlin.properties.Delegates internal class WalletViewModel @Inject constructor( @Suppress("unused") // TODO: [REDACTED_JIRA] private val getTokenListUseCase: GetTokenListUseCase, + private val scanCardProcessor: ScanCardProcessor, + private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel() { /** Feature router */ @@ -34,16 +42,32 @@ internal class WalletViewModel @Inject constructor( // TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData private fun getInitialState(): WalletStateHolder = WalletPreviewData.multicurrencyWalletScreenState.copy( - onBackClick = { router.popBackStack() }, - topBarConfig = WalletTopBarConfig( - onScanCardClick = { router.openOrganizeTokensScreen() }, - onMoreClick = { router.openDetailsScreen() }, - ), + onBackClick = ::onBackClick, + topBarConfig = createTopBarConfig(), walletsListConfig = WalletPreviewData.multicurrencyWalletScreenState.walletsListConfig.copy( onWalletChange = ::selectWallet, ), ) + private fun onBackClick() { + router.popBackStack() + } + + private fun createTopBarConfig(): WalletTopBarConfig { + return WalletTopBarConfig( + onScanCardClick = ::onScanCardClick, + onMoreClick = { router.openDetailsScreen() }, + ) + } + + private fun onScanCardClick() { + viewModelScope.launch(dispatchers.io) { + scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true) + .doOnSuccess { /* [REDACTED_TODO_COMMENT] */ } + .doOnFailure { /* [REDACTED_TODO_COMMENT] */ } + } + } + // TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData private fun selectWallet(index: Int) { if (uiState.walletsListConfig.selectedWalletIndex == index) return From cdd80826450c879c6ed9496392950ab689315519 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 7 Jul 2023 12:09:55 +0300 Subject: [PATCH 29/58] Updated on 2026-08-14 --- .../domain/tokens/GetTokenListUseCase.kt | 75 +++++++- .../tangem/domain/tokens/error/TokensError.kt | 15 ++ .../utils/TokenListFiatBalanceOperations.kt | 88 ++++++++++ .../tokens/utils/TokenListOperations.kt | 161 ++++++++++++++++++ .../tokens/utils/TokenStatusOperations.kt | 84 +++++++++ .../tokens/utils/TokensStatusesOperations.kt | 93 ++++++++++ 6 files changed, 511 insertions(+), 5 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index 0f52a195f9..024c43ef42 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -1,19 +1,24 @@ -// TODO: [REDACTED_JIRA] -@file:Suppress("unused", "unused_parameter") - package com.tangem.domain.tokens import arrow.core.Either import arrow.core.left +import arrow.core.raise.Raise +import arrow.core.raise.recover +import arrow.core.right import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.TokenStatus import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.TokensRepository +import com.tangem.domain.tokens.utils.TokenListFiatBalanceOperations +import com.tangem.domain.tokens.utils.TokenListOperations +import com.tangem.domain.tokens.utils.TokensStatusesOperations import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.flatMapConcat class GetTokenListUseCase( private val tokensRepository: TokensRepository, @@ -23,6 +28,66 @@ class GetTokenListUseCase( ) { operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow> { - return flowOf(TokensError.EmptyTokens.left()) + return channelFlow { + recover( + block = { + getTokenList(userWalletId, refresh).collect { list -> + send(list.right()) + } + }, + recover = { error -> + send(error.left()) + }, + ) + } + } + private fun Raise.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow { + return getTokensStatuses(userWalletId, refresh).flatMapConcat { tokens -> + createTokenList(userWalletId, tokens) + } + } + + private fun Raise.getTokensStatuses( + userWalletId: UserWalletId, + refresh: Boolean, + ): Flow> { + val operations = TokensStatusesOperations( + tokensRepository, + quotesRepository, + networksRepository, + userWalletId, + refresh, + dispatchers, + raise = this, + ) + + return operations.getTokensStatusesFlow() + } + + private suspend fun Raise.createTokenList( + userWalletId: UserWalletId, + tokens: Set, + ): Flow { + val isAnyTokenLoading = tokens.any { it.value is TokenStatus.Loading } + val operations = TokenListOperations( + tokensRepository, + networksRepository, + userWalletId, + calculateFiatBalance(tokens, isAnyTokenLoading), + isAnyTokenLoading, + dispatchers, + raise = this, + ) + + return operations.getTokenListFlow(tokens) + } + + private suspend fun calculateFiatBalance( + tokens: Set, + isAnyTokenLoading: Boolean, + ): TokenList.FiatBalance { + val operations = TokenListFiatBalanceOperations(tokens, isAnyTokenLoading, dispatchers) + + return operations.calculateFiatBalance() } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt index 4647779219..6571bc1d78 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt @@ -1,6 +1,21 @@ package com.tangem.domain.tokens.error +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.Token + sealed class TokensError { object EmptyTokens : TokensError() + + object EmptyQuotes : TokensError() + + object EmptyNetworks : TokensError() + + object EmptyNetworkStatues : TokensError() + + data class TokenAmountNotFound(val tokenId: Token.ID) : TokensError() + + data class TokenFiatAmountLessThenZero(val tokenId: Token.ID) : TokensError() + + data class NetworkNotFound(val networkId: Network.ID) : TokensError() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt new file mode 100644 index 0000000000..6d3360512e --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt @@ -0,0 +1,88 @@ +package com.tangem.domain.tokens.utils + +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import java.math.BigDecimal + +internal class TokenListFiatBalanceOperations( + private val tokens: Set, + private val isAnyTokenLoading: Boolean, + private val dispatcher: CoroutineDispatcherProvider, +) { + + suspend fun calculateFiatBalance(): TokenList.FiatBalance { + return withContext(dispatcher.single) { + var fiatBalance: TokenList.FiatBalance = TokenList.FiatBalance.Loading + if (tokens.isEmpty() || isAnyTokenLoading) return@withContext fiatBalance + + for (token in tokens) { + when (val status = token.value) { + is TokenStatus.Loading -> { + fiatBalance = TokenList.FiatBalance.Loading + break + } + is TokenStatus.MissedDerivation, + is TokenStatus.Unreachable, + -> { + fiatBalance = TokenList.FiatBalance.Failed + break + } + is TokenStatus.NoAccount -> { + fiatBalance = recalculateBalanceForNoAccountStatus(fiatBalance) + } + is TokenStatus.Loaded -> { + fiatBalance = recalculateBalance(status, fiatBalance) + } + is TokenStatus.Custom -> { + fiatBalance = recalculateBalance(status, fiatBalance) + } + } + } + + fiatBalance + } + } + private fun recalculateBalanceForNoAccountStatus(currentBalance: TokenList.FiatBalance): TokenList.FiatBalance { + return with(currentBalance) { + (this as? TokenList.FiatBalance.Loaded)?.copy( + isAllAmountsSummarized = false, + ) ?: TokenList.FiatBalance.Loaded( + amount = BigDecimal.ZERO, + isAllAmountsSummarized = false, + ) + } + } + + private fun recalculateBalance( + status: TokenStatus.Loaded, + currentBalance: TokenList.FiatBalance, + ): TokenList.FiatBalance { + return with(currentBalance) { + (this as? TokenList.FiatBalance.Loaded)?.copy( + amount = this.amount + status.fiatAmount, + ) ?: TokenList.FiatBalance.Loaded( + amount = status.fiatAmount, + isAllAmountsSummarized = true, + ) + } + } + + private fun recalculateBalance( + status: TokenStatus.Custom, + currentBalance: TokenList.FiatBalance, + ): TokenList.FiatBalance { + return with(currentBalance) { + val isTokenAmountCanBeSummarized = status.fiatAmount != null + + (this as? TokenList.FiatBalance.Loaded)?.copy( + amount = this.amount + (status.fiatAmount ?: BigDecimal.ZERO), + isAllAmountsSummarized = isTokenAmountCanBeSummarized, + ) ?: TokenList.FiatBalance.Loaded( + amount = status.fiatAmount ?: BigDecimal.ZERO, + isAllAmountsSummarized = isTokenAmountCanBeSummarized, + ) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt new file mode 100644 index 0000000000..eeec365e6b --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt @@ -0,0 +1,161 @@ +package com.tangem.domain.tokens.utils + +import arrow.core.NonEmptySet +import arrow.core.raise.Raise +import arrow.core.raise.ensureNotNull +import arrow.core.toNonEmptySetOrNull +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkGroup +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.TokensRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext +import java.math.BigDecimal + +@Suppress("LongParameterList") +internal class TokenListOperations( + private val tokensRepository: TokensRepository, + private val networksRepository: NetworksRepository, + private val userWalletId: UserWalletId, + private val totalFiatBalance: TokenList.FiatBalance, + private val isAnyTokenLoading: Boolean, + private val dispatchers: CoroutineDispatcherProvider, + raise: Raise, +) : Raise by raise { + + fun getTokenListFlow(tokens: Set): Flow { + return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance -> + createTokenList(tokens, isGrouped, isSortedByBalance) + } + } + + private suspend fun createTokenList( + tokens: Set, + isGrouped: Boolean, + isSortedByBalance: Boolean, + ): TokenList = withContext(dispatchers.single) { + val tokensNes = tokens.toNonEmptySetOrNull() + + when { + tokensNes == null -> TokenList.NotInitialized + isGrouped -> { + val networks = getNetworks(tokensNes) + + createGroupedTokenList(tokensNes, networks, isSortedByBalance) + } + else -> createUngroupedTokenList(tokensNes, isSortedByBalance) + } + } + + private fun createUngroupedTokenList( + tokens: NonEmptySet, + isSortedByBalance: Boolean, + ): TokenList.Ungrouped { + return TokenList.Ungrouped( + sortedBy = getSortType(isSortedByBalance), + totalFiatBalance = totalFiatBalance, + tokens = if (isSortedByBalance) sortTokensByBalance(tokens) else tokens, + ) + } + + private fun createGroupedTokenList( + tokens: NonEmptySet, + networks: NonEmptySet, + isSortedByBalance: Boolean, + ): TokenList.GroupedByNetwork { + return TokenList.GroupedByNetwork( + sortedBy = getSortType(isSortedByBalance), + totalFiatBalance = totalFiatBalance, + groups = if (isSortedByBalance) groupAndSortTokens(tokens, networks) else groupTokens(tokens, networks), + ) + } + + private fun groupAndSortTokens( + tokens: NonEmptySet, + networks: NonEmptySet, + ): NonEmptySet { + val groupsWithSortedTokens = groupTokens(tokens, networks) + .map { group -> + group.copy(tokens = sortTokensByBalance(group.tokens)) + } + val sortedGroups = if (isAnyTokenLoading) { + groupsWithSortedTokens + } else { + sortGroupsByBalance(groupsWithSortedTokens) + } + + return sortedGroups + } + + private fun sortTokensByBalance(tokens: NonEmptySet): NonEmptySet { + val sortedTokens = if (isAnyTokenLoading) { + tokens + } else { + tokens + .sortedByDescending { it.value.fiatAmount ?: BigDecimal.ZERO } + .toNonEmptySetOrNull() + } + + return sortedTokens!! + } + + private fun groupTokens( + tokens: NonEmptySet, + networks: NonEmptySet, + ): NonEmptySet { + val groups = tokens + .groupBy { it.networkId } + .map { (networkId, tokens) -> + val network = ensureNotNull(networks.firstOrNull { it.id == networkId }) { + TokensError.NetworkNotFound(networkId) + } + + NetworkGroup( + networkId = network.id, + name = network.name, + tokens = tokens.toNonEmptySetOrNull()!!, + ) + } + .toNonEmptySetOrNull() + + return groups!! + } + + private suspend fun getNetworks(tokens: NonEmptySet): NonEmptySet { + return withContext(dispatchers.io) { + val networks = networksRepository.getNetworks(tokens.map { it.networkId }).bind() + ensureNotNull(networks.toNonEmptySetOrNull()) { TokensError.EmptyNetworks } + } + } + + private fun getIsGrouped(): Flow { + return tokensRepository.isTokensGrouped(userWalletId) + .map { it.bind() } + .flowOn(dispatchers.io) + } + + private fun getIsSortedByBalance(): Flow { + return tokensRepository.isTokensSortedByBalance(userWalletId) + .map { it.bind() } + .flowOn(dispatchers.io) + } + + private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptySet): NonEmptySet { + return groupsWithSortedTokens + .sortedByDescending { group -> + group.tokens.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO } + } + .toNonEmptySetOrNull()!! + } + + private fun getSortType(isSortedByBalance: Boolean) = + if (isSortedByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt new file mode 100644 index 0000000000..09231e0526 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt @@ -0,0 +1,84 @@ +package com.tangem.domain.tokens.utils + +import arrow.core.raise.Raise +import arrow.core.raise.ensure +import arrow.core.raise.ensureNotNull +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.model.Quote +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import java.math.BigDecimal + +internal class TokenStatusOperations( + private val token: Token, + private val quote: Quote?, + private val networkStatus: NetworkStatus?, + private val dispatchers: CoroutineDispatcherProvider, + raise: Raise, +) : Raise by raise { + + suspend fun createTokenStatus(): TokenStatus = withContext(dispatchers.single) { + TokenStatus( + id = token.id, + networkId = token.networkId, + name = token.name, + symbol = token.symbol, + isCoin = token.isCoin, + value = createStatus(), + ) + } + + private fun createStatus(): TokenStatus.Status { + return when (val status = networkStatus?.value) { + null -> TokenStatus.Loading + is NetworkStatus.MissedDerivation -> TokenStatus.MissedDerivation + is NetworkStatus.Unreachable -> TokenStatus.Unreachable + is NetworkStatus.NoAccount -> TokenStatus.NoAccount + is NetworkStatus.TransactionInProgress, + is NetworkStatus.Verified, + -> createStatus( + amount = getTokenAmount(), + hasTransactionsInProgress = status is NetworkStatus.TransactionInProgress, + ) + } + } + + private fun createStatus(amount: BigDecimal, hasTransactionsInProgress: Boolean): TokenStatus.Status { + return when { + token.isCustom -> TokenStatus.Custom( + amount = amount, + fiatAmount = calculateFiatAmountOrNull(amount, quote?.fiatRate), + priceChange = quote?.priceChange, + hasTransactionsInProgress = hasTransactionsInProgress, + ) + quote == null -> TokenStatus.Loading + else -> TokenStatus.Loaded( + amount = amount, + fiatAmount = calculateFiatAmount(amount, quote.fiatRate), + priceChange = quote.priceChange, + hasTransactionsInProgress = hasTransactionsInProgress, + ) + } + } + + private fun getTokenAmount(): BigDecimal { + val amount = networkStatus?.value?.amounts?.get(token.id) + return ensureNotNull(amount) { TokensError.TokenAmountNotFound(token.id) } + } + + private fun calculateFiatAmountOrNull(amount: BigDecimal, fiatRate: BigDecimal?): BigDecimal? { + if (fiatRate == null) return null + + return calculateFiatAmount(amount, fiatRate) + } + + private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal { + val fiatAmount = amount * fiatRate + ensure(condition = fiatAmount >= BigDecimal.ZERO) { TokensError.TokenFiatAmountLessThenZero(token.id) } + + return fiatAmount + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt new file mode 100644 index 0000000000..e301066bc6 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt @@ -0,0 +1,93 @@ +package com.tangem.domain.tokens.utils + +import arrow.core.NonEmptySet +import arrow.core.raise.Raise +import arrow.core.toNonEmptySetOrNull +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.* +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.QuotesRepository +import com.tangem.domain.tokens.repository.TokensRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.withContext + +@Suppress("LongParameterList") +internal class TokensStatusesOperations( + private val tokensRepository: TokensRepository, + private val quotesRepository: QuotesRepository, + private val networksRepository: NetworksRepository, + private val userWalletId: UserWalletId, + private val refresh: Boolean, + private val dispatchers: CoroutineDispatcherProvider, + raise: Raise, +) : Raise by raise { + + fun getTokensStatusesFlow(): Flow> { + return getTokens().flatMapConcat { + val tokens = it.toNonEmptySetOrNull() + + if (tokens == null) { + flowOf(emptySet()) + } else { + val tokensIds = tokens.map { token -> token.id } + val groupedTokens = groupTokens(tokens) + + combine(getQuotes(tokensIds), getNetworksStatues(groupedTokens)) { quotes, networksStatuses -> + createTokensStatuses(tokens, quotes, networksStatuses) + } + } + } + } + + private suspend fun createTokensStatuses( + tokens: Set, + quotes: Set, + networkStatuses: Set, + ): Set = withContext(dispatchers.single) { + tokens.map { token -> + val quote = quotes.firstOrNull { it.tokenId == token.id } + val networkStatus = networkStatuses.firstOrNull { it.networkId == token.networkId } + + createStatus(token, quote, networkStatus) + }.toSet() + } + + private suspend fun createStatus(token: Token, quote: Quote?, networkStatus: NetworkStatus?): TokenStatus { + val operations = TokenStatusOperations(token, quote, networkStatus, dispatchers, raise = this) + return operations.createTokenStatus() + } + + private fun getTokens(): Flow> { + return tokensRepository.getTokens(userWalletId, refresh) + .onEmpty { raise(TokensError.EmptyTokens) } + .map { it.bind() } + .flowOn(dispatchers.io) + } + + private fun getQuotes(tokensIds: NonEmptySet): Flow> { + return quotesRepository.getQuotes(tokensIds, refresh) + .onEmpty { raise(TokensError.EmptyQuotes) } + .map { it.bind() } + .flowOn(dispatchers.io) + } + + private fun getNetworksStatues(groupedTokens: Map>): Flow> { + return networksRepository.getNetworkStatuses(userWalletId, groupedTokens, refresh) + .onEmpty { raise(TokensError.EmptyNetworkStatues) } + .map { it.bind() } + .flowOn(dispatchers.io) + } + + private suspend fun groupTokens(tokens: NonEmptySet): Map> = + withContext(dispatchers.single) { + tokens + .groupBy { it.networkId } + .mapValues { (_, tokens) -> + tokens.toNonEmptySetOrNull() + ?.map { it.id } + ?: raise(TokensError.EmptyTokens) + } + } +} \ No newline at end of file From 121160ed8e9f6525e097a6b9be306cec52a7150b Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Jul 2023 18:27:04 +0300 Subject: [PATCH 30/58] Updated on 2026-08-14 --- .../common/qrCodeScan/ScanQrCodeActivity.kt | 7 ++++++- .../details/ui/walletconnect/QrScanFragment.kt | 18 +++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) 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 f132cd319b..be7a6e4330 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 @@ -8,6 +8,7 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import com.google.zxing.BarcodeFormat import com.google.zxing.Result import com.otaliastudios.cameraview.CameraView.PERMISSION_REQUEST_CODE import me.dm7.barcodescanner.zxing.ZXingScannerView @@ -16,11 +17,15 @@ import me.dm7.barcodescanner.zxing.ZXingScannerView [REDACTED_AUTHOR] */ class ScanQrCodeActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { + private lateinit var mScannerView: ZXingScannerView override fun onCreate(state: Bundle?) { super.onCreate(state) - mScannerView = ZXingScannerView(this) + + mScannerView = ZXingScannerView(this).apply { + setFormats(listOf(BarcodeFormat.QR_CODE)) + } setContentView(mScannerView) if (!permissionIsGranted()) requestPermission() diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt index d67c7a262d..ccfe000c5c 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/walletconnect/QrScanFragment.kt @@ -11,6 +11,7 @@ import androidx.activity.OnBackPressedCallback import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat import androidx.fragment.app.Fragment +import com.google.zxing.BarcodeFormat import com.google.zxing.Result import com.otaliastudios.cameraview.CameraView import com.tangem.core.navigation.NavigationAction @@ -38,10 +39,19 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { if (!permissionIsGranted()) requestPermission() - scannerView = ZXingScannerView(activity) + scannerView = ZXingScannerView(activity).apply { + setFormats(listOf(BarcodeFormat.QR_CODE)) + } + return scannerView } + override fun onResume() { + super.onResume() + scannerView?.setResultHandler(this) + scannerView?.startCamera() + } + override fun onPause() { super.onPause() scannerView?.stopCamera() @@ -52,12 +62,6 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler { setFitSystemWindows(fit = false) } - override fun onResume() { - super.onResume() - scannerView?.setResultHandler(this) - scannerView?.startCamera() - } - override fun handleResult(result: Result) { store.dispatch(NavigationAction.PopBackTo()) setFitSystemWindows(fit = false) From d9b36792745ef9728004c59bfec01252b9a6ebef Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 7 Jul 2023 12:09:55 +0300 Subject: [PATCH 31/58] Updated on 2026-08-14 --- domain/tokens/build.gradle.kts | 3 + .../domain/tokens/GetTokenListUseCaseTest.kt | 334 ++++++++++++++++++ .../tangem/domain/tokens/mock/MockNetworks.kt | 90 +++++ .../tangem/domain/tokens/mock/MockQuotes.kt | 71 ++++ .../domain/tokens/mock/MockTokenLists.kt | 107 ++++++ .../tangem/domain/tokens/mock/MockTokens.kt | 90 +++++ .../domain/tokens/mock/MockTokensStates.kt | 128 +++++++ .../repository/MockNetworksRepository.kt | 27 ++ .../tokens/repository/MockQuotesRepository.kt | 16 + .../tokens/repository/MockTokensRepository.kt | 34 ++ 10 files changed, 900 insertions(+) create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokens.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt create mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt diff --git a/domain/tokens/build.gradle.kts b/domain/tokens/build.gradle.kts index d4297e1590..d6c92daddb 100644 --- a/domain/tokens/build.gradle.kts +++ b/domain/tokens/build.gradle.kts @@ -7,4 +7,7 @@ dependencies { implementation(projects.domain.core) implementation(projects.domain.wallets.models) implementation(projects.core.utils) + + testImplementation(deps.test.junit) + testImplementation(deps.test.coroutine) } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt new file mode 100644 index 0000000000..e876c1924d --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -0,0 +1,334 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.mock.MockNetworks +import com.tangem.domain.tokens.mock.MockQuotes +import com.tangem.domain.tokens.mock.MockTokenLists +import com.tangem.domain.tokens.mock.MockTokens +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.model.Quote +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.tokens.repository.MockNetworksRepository +import com.tangem.domain.tokens.repository.MockQuotesRepository +import com.tangem.domain.tokens.repository.MockTokensRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider +import junit.framework.TestCase.assertEquals +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) +internal class GetTokenListUseCaseTest { + + private val dispatchers = TestingCoroutineDispatcherProvider() + private val userWalletId = UserWalletId(value = null) + + @Test + fun `when list ungrouped and unsorted then correct token list should be returned`() = runTest { + // Given + val expectedResult = MockTokenLists.ungroupedTokenList.right() + + val useCase = getUseCase( + isGrouped = flowOf(false.right()), + isSortedByBalance = flowOf(false.right()), + ) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when tokens getting failed then error should be received`() = runTest { + // Given + val expectedResult = TokensError.EmptyTokens.left() + + val useCase = getUseCase(tokens = flowOf(expectedResult)) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when quotes getting failed then error should be received`() = runTest { + // Given + val expectedResult = TokensError.EmptyQuotes.left() + + val useCase = getUseCase(quotes = flowOf(expectedResult)) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when networks getting failed and list is groped then error should be received`() = runTest { + // Given + val expectedResult = TokensError.EmptyNetworkStatues.left() + + val useCase = getUseCase( + networks = expectedResult, + isGrouped = flowOf(true.right()), + ) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when networks statuses getting failed then error should be received`() = runTest { + // Given + val expectedResult = TokensError.EmptyNetworkStatues.left() + + val useCase = getUseCase(statuses = flowOf(expectedResult)) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when grouping type getting failed then error should be received`() = runTest { + // Given + val expectedResult = TokensError.EmptyTokens.left() + + val useCase = getUseCase(isGrouped = flowOf(expectedResult)) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when sorting type getting failed then error should be received`() = runTest { + // Given + val expectedResult = TokensError.EmptyTokens.left() + + val useCase = getUseCase(isSortedByBalance = flowOf(expectedResult)) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when tokens getting failed on second emit then error should be received`() = runTest { + // Given + val error = TokensError.EmptyTokens.left() + val expectedResult = listOf( + MockTokenLists.ungroupedTokenList.right(), + error, + ) + + val useCase = getUseCase( + tokens = flowOf( + MockTokens.tokens.right(), + error, + ), + ) + + // When + val result = useCase(userWalletId) + .take(count = 2) + .toList() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when list grouped then correct token list should be received`() = runTest { + val expectedResult = MockTokenLists.groupedTokenList.right() + + val useCase = getUseCase(isGrouped = flowOf(true.right())) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when list is grouped and networks getting failed then error should be received`() = runTest { + val expectedResult = TokensError.EmptyNetworks.left() + + val useCase = getUseCase( + networks = expectedResult, + isGrouped = flowOf(true.right()), + ) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when list is sorted and ungrouped then correct token list should be received`() = runTest { + val expectedResult = MockTokenLists.sortedUngroupedTokenList.right() + + val useCase = getUseCase( + statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), + isGrouped = flowOf(false.right()), + isSortedByBalance = flowOf(true.right()), + ) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when list is sorted and grouped then correct token list should be received`() = runTest { + val expectedResult = MockTokenLists.sortedGroupedTokenList.right() + + val useCase = getUseCase( + statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), + isGrouped = flowOf(true.right()), + isSortedByBalance = flowOf(true.right()), + ) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when tokens is empty then not initialized token list should be received`() = runTest { + val expectedResult = MockTokenLists.notInitializedTokenList.right() + + val useCase = getUseCase(tokens = flowOf(emptySet().right())) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when networks is empty and list is grouped then error should be received`() = runTest { + val expectedResult = TokensError.EmptyNetworks.left() + + val useCase = getUseCase( + networks = emptySet().right(), + isGrouped = flowOf(true.right()), + ) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when tokens flow is empty then error should be received`() = runTest { + val expectedResult = TokensError.EmptyTokens.left() + + val useCase = getUseCase(tokens = flowOf()) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when networks statuses flow is empty then error should be received`() = runTest { + val expectedResult = TokensError.EmptyNetworkStatues.left() + + val useCase = getUseCase(statuses = flowOf()) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when networks statuses is empty then loading token list should be received`() = runTest { + val expectedResult = MockTokenLists.loadingUngroupedTokenList.right() + + val useCase = getUseCase(statuses = flowOf(emptySet().right())) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when quotes flow is empty then error should be received`() = runTest { + val expectedResult = TokensError.EmptyQuotes.left() + + val useCase = getUseCase(quotes = flowOf()) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + @Test + fun `when quotes is empty and statuses verified then loading token list should be received`() = runTest { + val expectedResult = MockTokenLists.loadingUngroupedTokenList.right() + + val useCase = getUseCase( + statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), + quotes = flowOf(emptySet().right()), + ) + + // When + val result = useCase(userWalletId).first() + + // Then + assertEquals(expectedResult, result) + } + + private fun getUseCase( + tokens: Flow>> = flowOf(MockTokens.tokens.right()), + quotes: Flow>> = flowOf(MockQuotes.quotes.right()), + networks: Either> = MockNetworks.networks.right(), + statuses: Flow>> = flowOf(MockNetworks.errorNetworksStatuses.right()), + isGrouped: Flow> = flowOf(MockTokenLists.isGrouped.right()), + isSortedByBalance: Flow> = flowOf(MockTokenLists.isSortedByBalance.right()), + ) = GetTokenListUseCase( + dispatchers = dispatchers, + tokensRepository = MockTokensRepository(tokens, isGrouped, isSortedByBalance), + quotesRepository = MockQuotesRepository(quotes), + networksRepository = MockNetworksRepository(networks, statuses), + ) +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt new file mode 100644 index 0000000000..03ad43ba70 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockNetworks.kt @@ -0,0 +1,90 @@ +package com.tangem.domain.tokens.mock + +import arrow.core.NonEmptySet +import arrow.core.nonEmptySetOf +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import java.math.BigDecimal + +@Suppress("MemberVisibilityCanBePrivate") +internal object MockNetworks { + + val amountToCreateAccount: BigDecimal = BigDecimal.TEN + + val network1 = Network( + id = Network.ID("network1"), + name = "Network One", + ) + + val network2 = Network( + id = Network.ID("network2"), + name = "Network Two", + ) + + val network3 = Network( + id = Network.ID("network3"), + name = "Network Three", + ) + + val networks = nonEmptySetOf(network1, network2, network3) + + val networkStatus1 = NetworkStatus( + networkId = network1.id, + value = NetworkStatus.Unreachable, + ) + + val networkStatus2 = NetworkStatus( + networkId = network2.id, + value = NetworkStatus.MissedDerivation, + ) + + val networkStatus3 = NetworkStatus( + networkId = network3.id, + value = NetworkStatus.NoAccount( + amountToCreateAccount = amountToCreateAccount, + ), + ) + + val errorNetworksStatuses = nonEmptySetOf(networkStatus1, networkStatus2, networkStatus3) + + val verifiedNetworkStatus1: NetworkStatus + get() = networkStatus1.copy( + value = NetworkStatus.Verified( + amounts = mapOf( + MockTokens.token1.id to BigDecimal.TEN, + MockTokens.token2.id to BigDecimal.TEN, + MockTokens.token3.id to BigDecimal.TEN, + ), + ), + ) + + val verifiedNetworkStatus2: NetworkStatus + get() = networkStatus2.copy( + value = NetworkStatus.Verified( + amounts = mapOf( + MockTokens.token4.id to BigDecimal.TEN, + MockTokens.token5.id to BigDecimal.TEN, + MockTokens.token6.id to BigDecimal.TEN, + ), + ), + ) + + val verifiedNetworkStatus3: NetworkStatus + get() = networkStatus3.copy( + value = NetworkStatus.Verified( + amounts = mapOf( + MockTokens.token7.id to BigDecimal.TEN, + MockTokens.token8.id to BigDecimal.TEN, + MockTokens.token9.id to BigDecimal.TEN, + MockTokens.token10.id to BigDecimal.TEN, + ), + ), + ) + + val verifiedNetworksStatuses: NonEmptySet + get() = nonEmptySetOf( + verifiedNetworkStatus1, + verifiedNetworkStatus2, + verifiedNetworkStatus3, + ) +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt new file mode 100644 index 0000000000..767391423e --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockQuotes.kt @@ -0,0 +1,71 @@ +package com.tangem.domain.tokens.mock + +import arrow.core.nonEmptySetOf +import com.tangem.domain.tokens.model.Quote +import java.math.BigDecimal + +@Suppress("MemberVisibilityCanBePrivate") +internal object MockQuotes { + + val quote1 = Quote( + tokenId = MockTokens.token1.id, + fiatRate = BigDecimal("1.23"), + priceChange = BigDecimal("0.01"), + ) + + val quote2 = Quote( + tokenId = MockTokens.token2.id, + fiatRate = BigDecimal("2.34"), + priceChange = BigDecimal("-0.02"), + ) + + val quote3 = Quote( + tokenId = MockTokens.token3.id, + fiatRate = BigDecimal("3.45"), + priceChange = BigDecimal("0.03"), + ) + + val quote4 = Quote( + tokenId = MockTokens.token4.id, + fiatRate = BigDecimal("4.56"), + priceChange = BigDecimal("-0.04"), + ) + + val quote5 = Quote( + tokenId = MockTokens.token5.id, + fiatRate = BigDecimal("5.67"), + priceChange = BigDecimal("0.05"), + ) + + val quote6 = Quote( + tokenId = MockTokens.token6.id, + fiatRate = BigDecimal("6.78"), + priceChange = BigDecimal("-0.06"), + ) + + val quote7 = Quote( + tokenId = MockTokens.token7.id, + fiatRate = BigDecimal("7.89"), + priceChange = BigDecimal("0.07"), + ) + + val quote8 = Quote( + tokenId = MockTokens.token8.id, + fiatRate = BigDecimal("8.90"), + priceChange = BigDecimal("-0.08"), + ) + + val quote9 = Quote( + tokenId = MockTokens.token9.id, + fiatRate = BigDecimal("9.01"), + priceChange = BigDecimal("0.09"), + ) + + val quote10 = Quote( + tokenId = MockTokens.token10.id, + fiatRate = BigDecimal("10.12"), + priceChange = BigDecimal("-0.10"), + ) + + val quotes = nonEmptySetOf(quote1, quote2, quote3, quote4, quote5, quote6, quote7, quote8, quote9, quote10) +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt new file mode 100644 index 0000000000..2951fbef39 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt @@ -0,0 +1,107 @@ +package com.tangem.domain.tokens.mock + +import arrow.core.nonEmptySetOf +import arrow.core.toNonEmptySetOrNull +import com.tangem.domain.tokens.model.NetworkGroup +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.TokenStatus +import java.math.BigDecimal + +@Suppress("MemberVisibilityCanBePrivate") +internal object MockTokenLists { + + const val isGrouped = false + const val isSortedByBalance = false + + val networkGroup1 = NetworkGroup( + networkId = MockNetworks.network1.id, + name = MockNetworks.network1.name, + tokens = MockTokensStates.tokenStates + .filter { it.networkId == MockNetworks.network1.id } + .toNonEmptySetOrNull()!!, + ) + + val networkGroup2 = NetworkGroup( + networkId = MockNetworks.network2.id, + name = MockNetworks.network2.name, + tokens = MockTokensStates.tokenStates + .filter { it.networkId == MockNetworks.network2.id } + .toNonEmptySetOrNull()!!, + ) + + val networkGroup3 = NetworkGroup( + networkId = MockNetworks.network3.id, + name = MockNetworks.network3.name, + tokens = MockTokensStates.tokenStates + .filter { it.networkId == MockNetworks.network3.id } + .toNonEmptySetOrNull()!!, + ) + + val networksGroups = nonEmptySetOf(networkGroup1, networkGroup2, networkGroup3) + + val sortedNetworksGroups = networksGroups.map { group -> + group.copy( + tokens = MockTokensStates.loadedTokensStates + .filter { it.networkId == group.networkId } + .sortedByDescending { it.value.fiatAmount } + .toNonEmptySetOrNull()!!, + ) + } + .sortedByDescending { group -> + group.tokens.sumOf { it.value.fiatAmount!! } + } + .toNonEmptySetOrNull()!! + + val notInitializedTokenList = TokenList.NotInitialized + + val groupedTokenList = TokenList.GroupedByNetwork( + groups = networksGroups, + totalFiatBalance = TokenList.FiatBalance.Failed, + sortedBy = TokenList.SortType.NONE, + ) + + val ungroupedTokenList = TokenList.Ungrouped( + tokens = MockTokensStates.tokenStates, + totalFiatBalance = TokenList.FiatBalance.Failed, + sortedBy = TokenList.SortType.NONE, + ) + + val loadingUngroupedTokenList = with(ungroupedTokenList) { + copy( + tokens = tokens.map { it.copy(value = TokenStatus.Loading) }, + totalFiatBalance = TokenList.FiatBalance.Loading, + ) + } + + val sortedUngroupedTokenList: TokenList.Ungrouped + get() { + val tokens = MockTokensStates.loadedTokensStates + .sortedByDescending { it.value.fiatAmount } + .toNonEmptySetOrNull()!! + + return ungroupedTokenList.copy( + tokens = tokens, + sortedBy = TokenList.SortType.BALANCE, + totalFiatBalance = TokenList.FiatBalance.Loaded( + amount = tokens.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO }, + isAllAmountsSummarized = true, + ), + ) + } + + val sortedGroupedTokenList: TokenList.GroupedByNetwork + get() { + val groups = sortedNetworksGroups + + return groupedTokenList.copy( + groups = groups, + sortedBy = TokenList.SortType.BALANCE, + totalFiatBalance = TokenList.FiatBalance.Loaded( + amount = groups + .flatMap { it.tokens } + .sumOf { it.value.fiatAmount ?: BigDecimal.ZERO }, + isAllAmountsSummarized = true, + ), + ) + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokens.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokens.kt new file mode 100644 index 0000000000..e57f0f2471 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokens.kt @@ -0,0 +1,90 @@ +package com.tangem.domain.tokens.mock + +import arrow.core.nonEmptySetOf +import com.tangem.domain.tokens.model.Token + +internal object MockTokens { + + val token1 = Token( + id = Token.ID("token1"), + networkId = MockNetworks.network1.id, + name = "Token 1", + symbol = "T1", + isCustom = false, + isCoin = true, + ) + val token2 = Token( + id = Token.ID("token2"), + networkId = MockNetworks.network1.id, + name = "Token 2", + symbol = "T2", + isCustom = false, + isCoin = false, + ) + val token3 = Token( + id = Token.ID("token3"), + networkId = MockNetworks.network1.id, + name = "Token 3", + symbol = "T3", + isCustom = false, + isCoin = false, + ) + val token4 = Token( + id = Token.ID("token4"), + networkId = MockNetworks.network2.id, + name = "Token 4", + symbol = "T4", + isCustom = false, + isCoin = true, + ) + val token5 = Token( + id = Token.ID("token5"), + networkId = MockNetworks.network2.id, + name = "Token 5", + symbol = "T5", + isCustom = false, + isCoin = true, + ) + val token6 = Token( + id = Token.ID("token6"), + networkId = MockNetworks.network2.id, + name = "Token 6", + symbol = "T6", + isCustom = false, + isCoin = false, + ) + val token7 = Token( + id = Token.ID("token7"), + networkId = MockNetworks.network3.id, + name = "Token 7", + symbol = "T7", + isCustom = false, + isCoin = false, + ) + val token8 = Token( + id = Token.ID("token8"), + networkId = MockNetworks.network3.id, + name = "Token 8", + symbol = "T8", + isCustom = false, + isCoin = true, + ) + val token9 = Token( + id = Token.ID("token9"), + networkId = MockNetworks.network3.id, + name = "Token 9", + symbol = "T9", + isCustom = false, + isCoin = true, + ) + val token10 = Token( + id = Token.ID("token10"), + networkId = MockNetworks.network3.id, + name = "Token 10", + symbol = "T10", + isCustom = false, + isCoin = false, + ) + + val tokens = nonEmptySetOf(token1, token2, token3, token4, token5, token6, token7, token8, token9, token10) +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt new file mode 100644 index 0000000000..338b4e1698 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt @@ -0,0 +1,128 @@ +package com.tangem.domain.tokens.mock + +import arrow.core.nonEmptySetOf +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.model.TokenStatus + +@Suppress("MemberVisibilityCanBePrivate") +internal object MockTokensStates { + + val tokenState1 = TokenStatus( + id = MockTokens.token1.id, + networkId = MockTokens.token1.networkId, + name = MockTokens.token1.name, + symbol = MockTokens.token1.symbol, + isCoin = MockTokens.token1.isCoin, + value = TokenStatus.Unreachable, + ) + + val tokenState2 = TokenStatus( + id = MockTokens.token2.id, + networkId = MockTokens.token2.networkId, + name = MockTokens.token2.name, + symbol = MockTokens.token2.symbol, + isCoin = MockTokens.token2.isCoin, + value = TokenStatus.Unreachable, + ) + + val tokenState3 = TokenStatus( + id = MockTokens.token3.id, + networkId = MockTokens.token3.networkId, + name = MockTokens.token3.name, + symbol = MockTokens.token3.symbol, + isCoin = MockTokens.token3.isCoin, + value = TokenStatus.Unreachable, + ) + + val tokenState4 = TokenStatus( + id = MockTokens.token4.id, + networkId = MockTokens.token4.networkId, + name = MockTokens.token4.name, + symbol = MockTokens.token4.symbol, + isCoin = MockTokens.token4.isCoin, + value = TokenStatus.MissedDerivation, + ) + + val tokenState5 = TokenStatus( + id = MockTokens.token5.id, + networkId = MockTokens.token5.networkId, + name = MockTokens.token5.name, + symbol = MockTokens.token5.symbol, + isCoin = MockTokens.token5.isCoin, + value = TokenStatus.MissedDerivation, + ) + + val tokenState6 = TokenStatus( + id = MockTokens.token6.id, + networkId = MockTokens.token6.networkId, + name = MockTokens.token6.name, + symbol = MockTokens.token6.symbol, + isCoin = MockTokens.token6.isCoin, + value = TokenStatus.MissedDerivation, + ) + + val tokenState7 = TokenStatus( + id = MockTokens.token7.id, + networkId = MockTokens.token7.networkId, + name = MockTokens.token7.name, + symbol = MockTokens.token7.symbol, + isCoin = MockTokens.token7.isCoin, + value = TokenStatus.NoAccount, + ) + + val tokenState8 = TokenStatus( + id = MockTokens.token8.id, + networkId = MockTokens.token8.networkId, + name = MockTokens.token8.name, + symbol = MockTokens.token8.symbol, + isCoin = MockTokens.token8.isCoin, + value = TokenStatus.NoAccount, + ) + + val tokenState9 = TokenStatus( + id = MockTokens.token9.id, + networkId = MockTokens.token9.networkId, + name = MockTokens.token9.name, + symbol = MockTokens.token9.symbol, + isCoin = MockTokens.token9.isCoin, + value = TokenStatus.NoAccount, + ) + + val tokenState10 = TokenStatus( + id = MockTokens.token10.id, + networkId = MockTokens.token10.networkId, + name = MockTokens.token10.name, + symbol = MockTokens.token10.symbol, + isCoin = MockTokens.token10.isCoin, + value = TokenStatus.NoAccount, + ) + + val tokenStates = nonEmptySetOf( + tokenState1, + tokenState2, + tokenState3, + tokenState4, + tokenState5, + tokenState6, + tokenState7, + tokenState8, + tokenState9, + tokenState10, + ) + + val loadedTokensStates = tokenStates.map { state -> + val networkStatus = MockNetworks.verifiedNetworksStatuses.first { it.networkId == state.networkId } + val amount = (networkStatus.value as NetworkStatus.Verified).amounts[state.id]!! + val quote = MockQuotes.quotes.first { it.tokenId == state.id } + val fiatAmount = amount * quote.fiatRate + + state.copy( + value = TokenStatus.Loaded( + amount = amount, + fiatAmount = fiatAmount, + priceChange = quote.priceChange, + hasTransactionsInProgress = false, + ), + ) + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt new file mode 100644 index 0000000000..3267a30ba2 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -0,0 +1,27 @@ +package com.tangem.domain.tokens.repository + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +internal class MockNetworksRepository( + private val networks: Either>, + private val statuses: Flow>>, +) : NetworksRepository { + + override fun getNetworks(networksIds: Set): Either> { + return networks + } + + override fun getNetworkStatuses( + userWalletId: UserWalletId, + networks: Map>, + refresh: Boolean, + ): Flow>> { + return statuses + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt new file mode 100644 index 0000000000..e82b0c0c82 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.tokens.repository + +import arrow.core.Either +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Quote +import com.tangem.domain.tokens.model.Token +import kotlinx.coroutines.flow.Flow + +internal class MockQuotesRepository( + private val quotes: Flow>>, +) : QuotesRepository { + + override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow>> { + return quotes + } +} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt new file mode 100644 index 0000000000..5a7a757179 --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt @@ -0,0 +1,34 @@ +package com.tangem.domain.tokens.repository + +import arrow.core.Either +import arrow.core.right +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +internal class MockTokensRepository( + private val tokens: Flow>>, + private val isGrouped: Flow>, + private val isSortedByBalance: Flow>, +) : TokensRepository { + + override suspend fun sortTokens( + userWalletId: UserWalletId, + sortedTokensIds: Set, + isGrouped: Boolean, + isSortedByBalance: Boolean, + ): Either = Unit.right() + + override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> { + return tokens + } + + override fun isTokensGrouped(userWalletId: UserWalletId): Flow> { + return isGrouped + } + + override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> { + return isSortedByBalance + } +} \ No newline at end of file From c1935b59382dfbfdb7b9518f6999c85dc6054cf9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Jul 2023 22:52:06 +0300 Subject: [PATCH 32/58] Updated on 2026-08-14 --- .../walletconnect/WalletConnectSdkHelper.kt | 2 +- .../tangem/tap/features/demo/DemoHelper.kt | 4 ++-- .../redux/middlewares/RequestFeeMiddleware.kt | 2 +- .../send/redux/reducers/FeeReducer.kt | 7 +------ .../features/send/redux/states/FeeState.kt | 1 - .../wallet/ui/MultipleAddressUiHelper.kt | 19 +++++++++---------- .../wallet/ui/wallet/SingleWalletView.kt | 5 ++++- .../CurrencyExchangeManager.kt | 6 +++--- .../tap/proxy/TransactionManagerImpl.kt | 7 +++---- 9 files changed, 24 insertions(+), 29 deletions(-) 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 b1a6774b38..6466ac2f85 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 @@ -183,7 +183,7 @@ class WalletConnectSdkHelper { val dataToSign = EthereumUtils.buildTransactionToSign( transactionData = data.transaction, nonce = null, - blockchain = data.walletManager.wallet.blockchain + blockchain = data.walletManager.wallet.blockchain, ) ?: return null val command = SignHashCommand( 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 83ea251268..ef011c6dc1 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 @@ -92,8 +92,8 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact TransactionFee.Choosable( minimum = Fee.Common(Amount(minimumFee, blockchain)), normal = Fee.Common(Amount(normalFee, blockchain)), - priority = Fee.Common(Amount(priorityFee, blockchain)) - ) + priority = Fee.Common(Amount(priorityFee, blockchain)), + ), ) } 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 1b6769f0a8..efdd59f799 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 @@ -54,7 +54,7 @@ class RequestFeeMiddleware { val result = feeResult.data // val result = FeeMock.getFee(walletManager.wallet.blockchain) dispatch(FeeAction.FeeCalculation.SetFeeResult(result)) - when(result) { + when (result) { is TransactionFee.Single -> { val fee = result.normal.amount.value ?: BigDecimal.ZERO if (fee.isZero()) { 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 ba706cf281..7e2e65171e 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 @@ -1,6 +1,5 @@ package com.tangem.tap.features.send.redux.reducers -import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.tap.features.send.redux.FeeAction @@ -9,7 +8,6 @@ import com.tangem.tap.features.send.redux.SendScreenAction import com.tangem.tap.features.send.redux.states.FeeState import com.tangem.tap.features.send.redux.states.FeeType import com.tangem.tap.features.send.redux.states.SendState -import com.tangem.tap.features.send.redux.states.TransactionExtrasState import com.tangem.tap.features.wallet.redux.ProgressState /** @@ -34,7 +32,7 @@ class FeeReducer : SendInternalReducer { } state.copy( selectedFeeType = action.feeType, - currentFee = currentFee + currentFee = currentFee, ) } is FeeActionUi.ChangeIncludeFee -> state.copy(feeIsIncluded = action.isIncluded) @@ -43,7 +41,6 @@ class FeeReducer : SendInternalReducer { } private fun handleAction(action: FeeAction, sendState: SendState, state: FeeState): SendState { - val result = when (action) { is FeeAction.RequestFee -> { state.copy(progressState = ProgressState.Loading) @@ -122,6 +119,4 @@ class FeeReducer : SendInternalReducer { private fun getCurrentFeeType(state: FeeState): FeeType { return if (state.selectedFeeType == FeeType.SINGLE) FeeType.NORMAL else state.selectedFeeType } - - } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt index 1ae163571a..f7a3a5e41b 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.send.redux.states -import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.tap.common.analytics.events.AnalyticsParam 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 aa62852b75..c61cb57c3f 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,6 +7,14 @@ import com.tangem.wallet.R object MultipleAddressUiHelper { + private val blockchainsSupportingSplit = listOf( + Blockchain.Bitcoin, + Blockchain.BitcoinTestnet, + Blockchain.Litecoin, + Blockchain.BitcoinCash, + Blockchain.CardanoShelley, + ) + fun typeToId(type: AddressType, blockchain: Blockchain): Int { return if (blockchain in blockchainsSupportingSplit) { if (type == AddressType.Legacy) { @@ -22,7 +30,7 @@ object MultipleAddressUiHelper { fun idToType(id: Int, blockchain: Blockchain): AddressType? { return when (blockchain) { in blockchainsSupportingSplit -> { - when(id) { + when (id) { R.id.chip_default -> AddressType.Default R.id.chip_legacy -> AddressType.Legacy else -> null @@ -31,13 +39,4 @@ object MultipleAddressUiHelper { else -> null } } - - private val blockchainsSupportingSplit = listOf( - Blockchain.Bitcoin, - Blockchain.BitcoinTestnet, - Blockchain.Litecoin, - Blockchain.BitcoinCash, - Blockchain.CardanoShelley - ) - } \ 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 b3ca7e7f2b..23a4645bb2 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 @@ -190,7 +190,10 @@ class SingleWalletView : WalletView() { (binding.lAddress.root as? ViewGroup)?.beginDelayedTransition() chipGroupAddressType.show() chipGroupAddressType.fitChipsByGroupWidth() - val checkedId = MultipleAddressUiHelper.typeToId(primaryWallet.walletAddresses.selectedAddress.type, primaryWallet.currency.blockchain) + val checkedId = MultipleAddressUiHelper.typeToId( + primaryWallet.walletAddresses.selectedAddress.type, + primaryWallet.currency.blockchain, + ) if (checkedId != View.NO_ID) chipGroupAddressType.check(checkedId) chipGroupAddressType.setOnCheckedChangeListener { group, checkedId -> 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 b04348a966..d886b5873f 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 @@ -97,9 +97,9 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( val destinationAddress = token.contractAddress val feeResult = walletManager.getFee( - amountToSend, - destinationAddress, - ) as? Result.Success ?: return + amountToSend, + destinationAddress, + ) as? Result.Success ?: return val fee = feeResult.data.minimum val coinValue = walletManager.wallet.amounts[AmountType.Coin]?.value ?: BigDecimal.ZERO 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 63664407ff..fe86122123 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -226,7 +226,7 @@ class TransactionManagerImpl( ProxyFees( minFee = singleFee, normalFee = singleFee, - priorityFee = singleFee + priorityFee = singleFee, ) } is TransactionFee.Choosable -> { @@ -243,11 +243,10 @@ class TransactionManagerImpl( priorityFee = ProxyFee( gasLimit = BigInteger.ZERO, fee = convertToProxyAmount(amount = choosableFee.priority.amount), - ) + ), ) } } - } is Result.Failure -> { error(fee.error.message ?: fee.error.customMessage) @@ -310,7 +309,7 @@ class TransactionManagerImpl( gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit, fee = convertToProxyAmount(amount = choosableFee.priority.amount), ) - + ProxyFees( minFee = minProxyFee, normalFee = normalProxyFee, From 8a46d815b9fb0947f6e13213c5bd94c05dfe07f5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Jul 2023 08:59:18 +0500 Subject: [PATCH 33/58] Updated on 2026-08-14 --- .../marketprice/MarketPriceBlock.kt | 56 ++++++++++--------- .../marketprice/MarketPriceBlockState.kt | 17 ++++++ .../marketprice/PriceChangeConfig.kt | 9 +++ .../presentation/common/WalletPreviewData.kt | 21 ++++--- .../common/component/TokenItem.kt | 2 +- .../common/state/PriceChangeConfig.kt | 15 ----- .../common/state/TokenItemState.kt | 1 + .../state/WalletMarketplaceBlockState.kt | 33 ----------- .../wallet/state/WalletStateHolder.kt | 5 +- .../presentation/wallet/ui/WalletScreen.kt | 6 +- 10 files changed, 74 insertions(+), 91 deletions(-) rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/WalletMarketplaceBlock.kt => core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt (74%) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlockState.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/marketprice/PriceChangeConfig.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/PriceChangeConfig.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMarketplaceBlockState.kt diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/WalletMarketplaceBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt similarity index 74% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/WalletMarketplaceBlock.kt rename to core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt index 7033b42c61..d7f217308c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/WalletMarketplaceBlock.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency +package com.tangem.core.ui.components.marketprice import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -17,22 +17,17 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.Dp +import com.tangem.core.ui.R import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.common.state.PriceChangeConfig -import com.tangem.feature.wallet.presentation.wallet.state.WalletMarketplaceBlockState /** - * Wallet marketplace block - * - * @param state state - * -[REDACTED_AUTHOR] + * @see Figma component */ @Composable -internal fun WalletMarketplaceBlock(state: WalletMarketplaceBlockState, modifier: Modifier = Modifier) { +fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier) { var rootWidth by remember { mutableStateOf(value = 0) } Column( modifier = modifier @@ -54,12 +49,12 @@ internal fun WalletMarketplaceBlock(state: WalletMarketplaceBlockState, modifier ) when (state) { - is WalletMarketplaceBlockState.Loading -> { + is MarketPriceBlockState.Loading -> { RectangleShimmer( modifier = Modifier.size(width = TangemTheme.dimens.size158, height = TangemTheme.dimens.size20), ) } - is WalletMarketplaceBlockState.Content -> { + is MarketPriceBlockState.Content -> { Price( config = state, priceWidthDp = with(LocalDensity.current) { rootWidth.div(other = 2).toDp() }, @@ -70,7 +65,7 @@ internal fun WalletMarketplaceBlock(state: WalletMarketplaceBlockState, modifier } @Composable -private fun Price(config: WalletMarketplaceBlockState.Content, priceWidthDp: Dp) { +private fun Price(config: MarketPriceBlockState.Content, priceWidthDp: Dp) { Row( modifier = Modifier, verticalAlignment = Alignment.CenterVertically, @@ -124,35 +119,44 @@ private fun PriceChangeInPercent(config: PriceChangeConfig) { @Preview @Composable -private fun Preview_MarketplaceBlock_Light( - @PreviewParameter(WalletMarketplaceStateProvider::class) - state: WalletMarketplaceBlockState, +private fun Preview_MarketPriceBlock_Light( + @PreviewParameter(WalletMarketPriceBlockStateProvider::class) + state: MarketPriceBlockState, ) { TangemTheme(isDark = false) { - WalletMarketplaceBlock(state = state) + MarketPriceBlock(state = state) } } @Preview @Composable -private fun Preview_MarketplaceBlock_Dark( - @PreviewParameter(WalletMarketplaceStateProvider::class) - state: WalletMarketplaceBlockState, +private fun Preview_MarketPriceBlock_Dark( + @PreviewParameter(WalletMarketPriceBlockStateProvider::class) + state: MarketPriceBlockState, ) { TangemTheme(isDark = true) { - WalletMarketplaceBlock(state = state) + MarketPriceBlock(state = state) } } -private class WalletMarketplaceStateProvider : CollectionPreviewParameterProvider( +private class WalletMarketPriceBlockStateProvider : CollectionPreviewParameterProvider( collection = listOf( - WalletPreviewData.marketplaceBlockContent, - WalletPreviewData.marketplaceBlockContent.copy( + MarketPriceBlockState.Content( + currencyName = "BTC", + price = "98900", priceChangeConfig = PriceChangeConfig( valueInPercent = "5.16%", type = PriceChangeConfig.Type.DOWN, ), ), - WalletMarketplaceBlockState.Loading(currencyName = "BTC"), + MarketPriceBlockState.Content( + currencyName = "BTC", + price = "98900", + priceChangeConfig = PriceChangeConfig( + valueInPercent = "10.89%", + type = PriceChangeConfig.Type.UP, + ), + ), + MarketPriceBlockState.Loading(currencyName = "BTC"), ), ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlockState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlockState.kt new file mode 100644 index 0000000000..ae59401aa9 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlockState.kt @@ -0,0 +1,17 @@ +package com.tangem.core.ui.components.marketprice + +import androidx.compose.runtime.Immutable + +@Immutable +sealed interface MarketPriceBlockState { + + val currencyName: String + + data class Loading(override val currencyName: String) : MarketPriceBlockState + + data class Content( + override val currencyName: String, + val price: String, + val priceChangeConfig: PriceChangeConfig, + ) : MarketPriceBlockState +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/PriceChangeConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/PriceChangeConfig.kt new file mode 100644 index 0000000000..60af2367e5 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/PriceChangeConfig.kt @@ -0,0 +1,9 @@ +package com.tangem.core.ui.components.marketprice + +data class PriceChangeConfig(val valueInPercent: String, val type: Type) { + + /** Price changing type */ + enum class Type { + UP, DOWN + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index 6f6b726f82..c89bcc47ee 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -1,8 +1,9 @@ package com.tangem.feature.wallet.presentation.common import com.tangem.core.ui.R +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.core.ui.components.marketprice.PriceChangeConfig import com.tangem.core.ui.components.transactions.TransactionState -import com.tangem.feature.wallet.presentation.common.state.PriceChangeConfig import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem @@ -204,15 +205,6 @@ internal object WalletPreviewData { WalletManageButton.CopyAddress(onClick = {}), ) - val marketplaceBlockContent = WalletMarketplaceBlockState.Content( - currencyName = "BTC", - price = "0.11$", - priceChangeConfig = PriceChangeConfig( - valueInPercent = "5.16%", - type = PriceChangeConfig.Type.UP, - ), - ) - val multicurrencyWalletScreenState = WalletStateHolder.MultiCurrencyContent( onBackClick = {}, topBarConfig = walletTopBarConfig, @@ -300,6 +292,13 @@ internal object WalletPreviewData { ), notifications = persistentListOf(WalletNotification.LikeTangemApp(onClick = {})), buttons = manageButtons, - marketplaceBlockState = marketplaceBlockContent, + marketPriceBlockState = MarketPriceBlockState.Content( + currencyName = "BTC", + price = "98900.12$", + priceChangeConfig = PriceChangeConfig( + valueInPercent = "5.16%", + type = PriceChangeConfig.Type.UP, + ), + ), ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt index 62b877549a..5c4cb19fb7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -28,11 +28,11 @@ import androidx.constraintlayout.compose.Dimension import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.marketprice.PriceChangeConfig import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTypography import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.common.state.PriceChangeConfig import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState import org.burnoutcrew.reorderable.ReorderableLazyListState diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/PriceChangeConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/PriceChangeConfig.kt deleted file mode 100644 index 9d6b4989ee..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/PriceChangeConfig.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.feature.wallet.presentation.common.state - -/** - * Price changing config - * - * @property valueInPercent value in percent - * @property type type [Type] - */ -internal data class PriceChangeConfig(val valueInPercent: String, val type: Type) { - - /** Price changing type */ - enum class Type { - UP, DOWN - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt index 5489b5e602..b1c8417e9e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.common.state import androidx.annotation.DrawableRes import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.marketprice.PriceChangeConfig /** Token item state */ @Immutable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMarketplaceBlockState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMarketplaceBlockState.kt deleted file mode 100644 index 591c548d9e..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletMarketplaceBlockState.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.state - -import com.tangem.feature.wallet.presentation.common.state.PriceChangeConfig - -/** - * Wallet marketplace component state - * - * @property currencyName currency name - * -[REDACTED_AUTHOR] - */ -internal sealed class WalletMarketplaceBlockState(open val currencyName: String) { - - /** - * Loading state - * - * @property currencyName currency name - */ - data class Loading(override val currencyName: String) : WalletMarketplaceBlockState(currencyName = currencyName) - - /** - * Content state - * - * @property currencyName currency name - * @property price price - * @property priceChangeConfig price change config - */ - data class Content( - override val currencyName: String, - val price: String, - val priceChangeConfig: PriceChangeConfig, - ) : WalletMarketplaceBlockState(currencyName = currencyName) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index 02b9d37a3d..b9b216a198 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import kotlinx.collections.immutable.ImmutableList /** @@ -49,7 +50,7 @@ internal sealed class WalletStateHolder( * @property contentItems content items * @property notifications notifications * @property buttons manage buttons - * @property marketplaceBlockState marketplace block state + * @property marketPriceBlockState market price block state */ data class SingleCurrencyContent( override val onBackClick: () -> Unit, @@ -58,6 +59,6 @@ internal sealed class WalletStateHolder( override val contentItems: ImmutableList, override val notifications: ImmutableList, val buttons: ImmutableList, - val marketplaceBlockState: WalletMarketplaceBlockState, + val marketPriceBlockState: MarketPriceBlockState, ) : WalletStateHolder(onBackClick, topBarConfig, walletsListConfig, contentItems, notifications) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 438aaf5d32..b89486d90e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.buttons.actions.RoundedActionButton +import com.tangem.core.ui.components.marketprice.MarketPriceBlock import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.transactions.Transaction import com.tangem.core.ui.res.TangemTheme @@ -33,7 +34,6 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockGroupTitle import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockTitle import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.WalletManageButtons -import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.WalletMarketplaceBlock import com.tangem.feature.wallet.presentation.wallet.ui.decorations.walletContentItemDecoration import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator @@ -94,8 +94,8 @@ internal fun WalletScreen(state: WalletStateHolder) { if (state is WalletStateHolder.SingleCurrencyContent) { item { - WalletMarketplaceBlock( - state = state.marketplaceBlockState, + MarketPriceBlock( + state = state.marketPriceBlockState, modifier = changeableItemModifier .padding(top = TangemTheme.dimens.spacing14) .padding(horizontal = TangemTheme.dimens.spacing16), From 7872d47eda4f698ce2e00199482eb90c8bca46be Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Jul 2023 09:41:25 +0300 Subject: [PATCH 34/58] Updated on 2026-08-14 --- .../com/tangem/domain/tokens/model/Token.kt | 2 ++ .../tangem/domain/tokens/model/TokenStatus.kt | 2 ++ .../tokens/utils/TokenListOperations.kt | 4 ++- .../tokens/utils/TokenStatusOperations.kt | 2 ++ .../tokens/utils/TokensStatusesOperations.kt | 3 +- .../domain/tokens/mock/MockTokenLists.kt | 2 +- .../tangem/domain/tokens/mock/MockTokens.kt | 30 ++++++++++++++++--- .../domain/tokens/mock/MockTokensStates.kt | 20 +++++++++++++ gradle/dependencies.toml | 2 +- 9 files changed, 59 insertions(+), 8 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Token.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Token.kt index cbaa05d20e..4e1a2fba61 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Token.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/Token.kt @@ -5,6 +5,8 @@ data class Token( val networkId: Network.ID, val name: String, val symbol: String, + val iconUrl: String?, + val decimals: Int, val isCustom: Boolean, val isCoin: Boolean, ) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenStatus.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenStatus.kt index 153caf20ba..1ea03d8aa3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenStatus.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenStatus.kt @@ -7,6 +7,8 @@ data class TokenStatus( val networkId: Network.ID, val name: String, val symbol: String, + val decimals: Int, + val iconUrl: String?, val isCoin: Boolean, val value: Status, ) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt index eeec365e6b..01a126b942 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt @@ -86,6 +86,7 @@ internal class TokenListOperations( .map { group -> group.copy(tokens = sortTokensByBalance(group.tokens)) } + .toNonEmptySet() val sortedGroups = if (isAnyTokenLoading) { groupsWithSortedTokens } else { @@ -131,7 +132,8 @@ internal class TokenListOperations( private suspend fun getNetworks(tokens: NonEmptySet): NonEmptySet { return withContext(dispatchers.io) { - val networks = networksRepository.getNetworks(tokens.map { it.networkId }).bind() + val networksIds = tokens.map { it.networkId }.toNonEmptySet() + val networks = networksRepository.getNetworks(networksIds).bind() ensureNotNull(networks.toNonEmptySetOrNull()) { TokensError.EmptyNetworks } } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt index 09231e0526..c0ebeeafa7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt @@ -27,6 +27,8 @@ internal class TokenStatusOperations( name = token.name, symbol = token.symbol, isCoin = token.isCoin, + decimals = token.decimals, + iconUrl = token.iconUrl, value = createStatus(), ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt index e301066bc6..0c90a3c80b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt @@ -31,7 +31,7 @@ internal class TokensStatusesOperations( if (tokens == null) { flowOf(emptySet()) } else { - val tokensIds = tokens.map { token -> token.id } + val tokensIds = tokens.map { token -> token.id }.toNonEmptySet() val groupedTokens = groupTokens(tokens) combine(getQuotes(tokensIds), getNetworksStatues(groupedTokens)) { quotes, networksStatuses -> @@ -87,6 +87,7 @@ internal class TokensStatusesOperations( .mapValues { (_, tokens) -> tokens.toNonEmptySetOrNull() ?.map { it.id } + ?.toNonEmptySet() ?: raise(TokensError.EmptyTokens) } } diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt index 2951fbef39..78b4810d7b 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt @@ -68,7 +68,7 @@ internal object MockTokenLists { val loadingUngroupedTokenList = with(ungroupedTokenList) { copy( - tokens = tokens.map { it.copy(value = TokenStatus.Loading) }, + tokens = tokens.map { it.copy(value = TokenStatus.Loading) }.toNonEmptySet(), totalFiatBalance = TokenList.FiatBalance.Loading, ) } diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokens.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokens.kt index e57f0f2471..43f22db152 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokens.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokens.kt @@ -11,7 +11,10 @@ internal object MockTokens { name = "Token 1", symbol = "T1", isCustom = false, + decimals = 8, + iconUrl = null, isCoin = true, + ) val token2 = Token( id = Token.ID("token2"), @@ -19,6 +22,8 @@ internal object MockTokens { name = "Token 2", symbol = "T2", isCustom = false, + decimals = 8, + iconUrl = null, isCoin = false, ) val token3 = Token( @@ -27,6 +32,8 @@ internal object MockTokens { name = "Token 3", symbol = "T3", isCustom = false, + decimals = 8, + iconUrl = null, isCoin = false, ) val token4 = Token( @@ -35,7 +42,10 @@ internal object MockTokens { name = "Token 4", symbol = "T4", isCustom = false, + decimals = 8, + iconUrl = null, isCoin = true, + ) val token5 = Token( id = Token.ID("token5"), @@ -43,7 +53,9 @@ internal object MockTokens { name = "Token 5", symbol = "T5", isCustom = false, - isCoin = true, + decimals = 8, + iconUrl = null, + isCoin = false, ) val token6 = Token( id = Token.ID("token6"), @@ -51,6 +63,8 @@ internal object MockTokens { name = "Token 6", symbol = "T6", isCustom = false, + decimals = 8, + iconUrl = null, isCoin = false, ) val token7 = Token( @@ -59,7 +73,9 @@ internal object MockTokens { name = "Token 7", symbol = "T7", isCustom = false, - isCoin = false, + decimals = 8, + iconUrl = null, + isCoin = true, ) val token8 = Token( id = Token.ID("token8"), @@ -67,7 +83,9 @@ internal object MockTokens { name = "Token 8", symbol = "T8", isCustom = false, - isCoin = true, + decimals = 8, + iconUrl = null, + isCoin = false, ) val token9 = Token( id = Token.ID("token9"), @@ -75,7 +93,9 @@ internal object MockTokens { name = "Token 9", symbol = "T9", isCustom = false, - isCoin = true, + decimals = 8, + iconUrl = null, + isCoin = false, ) val token10 = Token( id = Token.ID("token10"), @@ -83,6 +103,8 @@ internal object MockTokens { name = "Token 10", symbol = "T10", isCustom = false, + decimals = 8, + iconUrl = null, isCoin = false, ) diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt index 338b4e1698..0b0c9e41ab 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt @@ -12,6 +12,8 @@ internal object MockTokensStates { networkId = MockTokens.token1.networkId, name = MockTokens.token1.name, symbol = MockTokens.token1.symbol, + decimals = MockTokens.token1.decimals, + iconUrl = MockTokens.token1.iconUrl, isCoin = MockTokens.token1.isCoin, value = TokenStatus.Unreachable, ) @@ -21,6 +23,8 @@ internal object MockTokensStates { networkId = MockTokens.token2.networkId, name = MockTokens.token2.name, symbol = MockTokens.token2.symbol, + decimals = MockTokens.token2.decimals, + iconUrl = MockTokens.token2.iconUrl, isCoin = MockTokens.token2.isCoin, value = TokenStatus.Unreachable, ) @@ -30,6 +34,8 @@ internal object MockTokensStates { networkId = MockTokens.token3.networkId, name = MockTokens.token3.name, symbol = MockTokens.token3.symbol, + decimals = MockTokens.token3.decimals, + iconUrl = MockTokens.token3.iconUrl, isCoin = MockTokens.token3.isCoin, value = TokenStatus.Unreachable, ) @@ -39,6 +45,8 @@ internal object MockTokensStates { networkId = MockTokens.token4.networkId, name = MockTokens.token4.name, symbol = MockTokens.token4.symbol, + decimals = MockTokens.token4.decimals, + iconUrl = MockTokens.token4.iconUrl, isCoin = MockTokens.token4.isCoin, value = TokenStatus.MissedDerivation, ) @@ -48,6 +56,8 @@ internal object MockTokensStates { networkId = MockTokens.token5.networkId, name = MockTokens.token5.name, symbol = MockTokens.token5.symbol, + decimals = MockTokens.token5.decimals, + iconUrl = MockTokens.token5.iconUrl, isCoin = MockTokens.token5.isCoin, value = TokenStatus.MissedDerivation, ) @@ -57,6 +67,8 @@ internal object MockTokensStates { networkId = MockTokens.token6.networkId, name = MockTokens.token6.name, symbol = MockTokens.token6.symbol, + decimals = MockTokens.token6.decimals, + iconUrl = MockTokens.token6.iconUrl, isCoin = MockTokens.token6.isCoin, value = TokenStatus.MissedDerivation, ) @@ -66,6 +78,8 @@ internal object MockTokensStates { networkId = MockTokens.token7.networkId, name = MockTokens.token7.name, symbol = MockTokens.token7.symbol, + decimals = MockTokens.token7.decimals, + iconUrl = MockTokens.token7.iconUrl, isCoin = MockTokens.token7.isCoin, value = TokenStatus.NoAccount, ) @@ -75,6 +89,8 @@ internal object MockTokensStates { networkId = MockTokens.token8.networkId, name = MockTokens.token8.name, symbol = MockTokens.token8.symbol, + decimals = MockTokens.token8.decimals, + iconUrl = MockTokens.token8.iconUrl, isCoin = MockTokens.token8.isCoin, value = TokenStatus.NoAccount, ) @@ -84,6 +100,8 @@ internal object MockTokensStates { networkId = MockTokens.token9.networkId, name = MockTokens.token9.name, symbol = MockTokens.token9.symbol, + decimals = MockTokens.token9.decimals, + iconUrl = MockTokens.token9.iconUrl, isCoin = MockTokens.token9.isCoin, value = TokenStatus.NoAccount, ) @@ -93,6 +111,8 @@ internal object MockTokensStates { networkId = MockTokens.token10.networkId, name = MockTokens.token10.name, symbol = MockTokens.token10.symbol, + decimals = MockTokens.token10.decimals, + iconUrl = MockTokens.token10.iconUrl, isCoin = MockTokens.token10.isCoin, value = TokenStatus.NoAccount, ) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index e7fc8fd730..9b0fbad374 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -71,7 +71,7 @@ zxingQrBarcodeScanner = "1.9.8" zxingQrCode = "3.5.1" mviCore = "1.3.1" kotlinSerialization = "1.4.1" -arrow = "1.2.0-RC" +arrow = "1.2.0" reactiveNetwork = "3.0.8" walletConnectCore = "1.17.0" walletConnectWeb3 = "1.10.0" From 2672b38f1258fe896515f575258c78c50d84b996 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Jul 2023 23:20:44 +0300 Subject: [PATCH 35/58] Updated on 2026-08-14 --- app/src/main/java/com/tangem/tap/TapApplication.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt index b1c5862c9b..00e2ae0a86 100644 --- a/app/src/main/java/com/tangem/tap/TapApplication.kt +++ b/app/src/main/java/com/tangem/tap/TapApplication.kt @@ -5,6 +5,7 @@ import android.content.Context import android.content.pm.PackageManager import coil.ImageLoader import coil.ImageLoaderFactory +import com.orhanobut.logger.AndroidLogAdapter import com.orhanobut.logger.Logger import com.tangem.Log import com.tangem.LogFormat @@ -179,6 +180,7 @@ class TapApplication : Application(), ImageLoaderFactory { ) if (BuildConfig.DEBUG) { + Logger.addLogAdapter(AndroidLogAdapter()) Timber.plant( object : Timber.DebugTree() { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { From 8e44c110bb2a4ba8bba9e01b03d5c105dcde1bee Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Jul 2023 09:44:52 +0300 Subject: [PATCH 36/58] Updated on 2026-08-14 --- .../tangem/data/tokens/di/TokensDataModule.kt | 12 +- .../tangem/data/tokens/mock/MockNetworks.kt | 56 ++++++++ .../com/tangem/data/tokens/mock/MockQuotes.kt | 70 ++++++++++ .../com/tangem/data/tokens/mock/MockTokens.kt | 132 ++++++++++++++++++ ...epository.kt => MockNetworksRepository.kt} | 17 ++- ...sRepository.kt => MockQuotesRepository.kt} | 12 +- ...sRepository.kt => MockTokensRepository.kt} | 16 ++- 7 files changed, 297 insertions(+), 18 deletions(-) create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockNetworks.kt create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockQuotes.kt create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockTokens.kt rename data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/{DefaultNetworksRepository.kt => MockNetworksRepository.kt} (60%) rename data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/{DefaultQuotesRepository.kt => MockQuotesRepository.kt} (56%) rename data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/{DefaultTokensRepository.kt => MockTokensRepository.kt} (60%) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 9f88733061..85b6f06d93 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -1,8 +1,8 @@ package com.tangem.data.tokens.di -import com.tangem.data.tokens.repository.DefaultNetworksRepository -import com.tangem.data.tokens.repository.DefaultQuotesRepository -import com.tangem.data.tokens.repository.DefaultTokensRepository +import com.tangem.data.tokens.repository.MockNetworksRepository +import com.tangem.data.tokens.repository.MockQuotesRepository +import com.tangem.data.tokens.repository.MockTokensRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.TokensRepository @@ -18,13 +18,13 @@ internal object TokensDataModule { @Provides @Singleton - fun provideTokensRepository(): TokensRepository = DefaultTokensRepository() + fun provideTokensRepository(): TokensRepository = MockTokensRepository() @Provides @Singleton - fun provideQuotesRepository(): QuotesRepository = DefaultQuotesRepository() + fun provideQuotesRepository(): QuotesRepository = MockQuotesRepository() @Provides @Singleton - fun provideNetworksRepository(): NetworksRepository = DefaultNetworksRepository() + fun provideNetworksRepository(): NetworksRepository = MockNetworksRepository() } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockNetworks.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockNetworks.kt new file mode 100644 index 0000000000..af85a6322b --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockNetworks.kt @@ -0,0 +1,56 @@ +package com.tangem.data.tokens.mock + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkStatus +import java.math.BigDecimal + +@Suppress("MemberVisibilityCanBePrivate") +internal object MockNetworks { + + val network1 = Network( + id = Network.ID("network1"), + name = "Network One", + ) + + val network2 = Network( + id = Network.ID("network2"), + name = "Network Two", + ) + + val network3 = Network( + id = Network.ID("network3"), + name = "Network Three", + ) + + val networks = setOf(network1, network2, network3) + + val networkStatus1 = NetworkStatus( + networkId = network1.id, + value = NetworkStatus.Verified( + amounts = mapOf( + MockTokens.token1.id to BigDecimal("123.1234556789"), + MockTokens.token2.id to BigDecimal("42.2"), + MockTokens.token3.id to BigDecimal("1000000000.5"), + ), + ), + ) + + val networkStatus2 = NetworkStatus( + networkId = network2.id, + value = NetworkStatus.MissedDerivation, + ) + + val networkStatus3 = NetworkStatus( + networkId = network3.id, + value = NetworkStatus.Verified( + amounts = mapOf( + MockTokens.token7.id to BigDecimal.ZERO, + MockTokens.token8.id to BigDecimal.TEN, + MockTokens.token9.id to BigDecimal.TEN, + MockTokens.token10.id to BigDecimal.TEN, + ), + ), + ) + + val networksStatuses = setOf(networkStatus1, networkStatus2, networkStatus3) +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockQuotes.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockQuotes.kt new file mode 100644 index 0000000000..86f7a6d76c --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockQuotes.kt @@ -0,0 +1,70 @@ +package com.tangem.data.tokens.mock + +import com.tangem.domain.tokens.model.Quote +import java.math.BigDecimal + +@Suppress("MemberVisibilityCanBePrivate") +internal object MockQuotes { + + val quote1 = Quote( + tokenId = MockTokens.token1.id, + fiatRate = BigDecimal("1.23"), + priceChange = BigDecimal("0.01"), + ) + + val quote2 = Quote( + tokenId = MockTokens.token2.id, + fiatRate = BigDecimal("2.34"), + priceChange = BigDecimal("-0.02"), + ) + + val quote3 = Quote( + tokenId = MockTokens.token3.id, + fiatRate = BigDecimal("3.45"), + priceChange = BigDecimal("0.03"), + ) + + val quote4 = Quote( + tokenId = MockTokens.token4.id, + fiatRate = BigDecimal("4.56"), + priceChange = BigDecimal("-0.04"), + ) + + val quote5 = Quote( + tokenId = MockTokens.token5.id, + fiatRate = BigDecimal("5.67"), + priceChange = BigDecimal("0.05"), + ) + + val quote6 = Quote( + tokenId = MockTokens.token6.id, + fiatRate = BigDecimal("6.78"), + priceChange = BigDecimal("-0.06"), + ) + + val quote7 = Quote( + tokenId = MockTokens.token7.id, + fiatRate = BigDecimal("7.89"), + priceChange = BigDecimal("0.07"), + ) + + val quote8 = Quote( + tokenId = MockTokens.token8.id, + fiatRate = BigDecimal("8.90"), + priceChange = BigDecimal("-0.08"), + ) + + val quote9 = Quote( + tokenId = MockTokens.token9.id, + fiatRate = BigDecimal("9.01"), + priceChange = BigDecimal("0.09"), + ) + + val quote10 = Quote( + tokenId = MockTokens.token10.id, + fiatRate = BigDecimal("10.12"), + priceChange = BigDecimal("-0.10"), + ) + + val quotes = setOf(quote1, quote2, quote3, quote4, quote5, quote6, quote7, quote8, quote9, quote10) +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockTokens.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockTokens.kt new file mode 100644 index 0000000000..4261727cff --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockTokens.kt @@ -0,0 +1,132 @@ +package com.tangem.data.tokens.mock + +import com.tangem.domain.tokens.model.Token +import com.tangem.domain.wallets.models.UserWalletId + +internal object MockTokens { + + val token1 get() = Token( + id = Token.ID("token1"), + networkId = MockNetworks.network1.id, + name = "Token 1", + symbol = "T1", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = true, + ) + val token2 get() = Token( + id = Token.ID("token2"), + networkId = MockNetworks.network1.id, + name = "Token 2", + symbol = "T2", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = false, + ) + val token3 get() = Token( + id = Token.ID("token3"), + networkId = MockNetworks.network1.id, + name = "Token 3", + symbol = "T3", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = false, + ) + val token4 get() = Token( + id = Token.ID("token4"), + networkId = MockNetworks.network2.id, + name = "Token 4", + symbol = "T4", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = true, + ) + val token5 get() = Token( + id = Token.ID("token5"), + networkId = MockNetworks.network2.id, + name = "Token 5", + symbol = "T5", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = false, + ) + val token6 get() = Token( + id = Token.ID("token6"), + networkId = MockNetworks.network2.id, + name = "Token 6", + symbol = "T6", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = false, + ) + val token7 get() = Token( + id = Token.ID("token7"), + networkId = MockNetworks.network3.id, + name = "Token 7", + symbol = "T7", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = true, + ) + val token8 get() = Token( + id = Token.ID("token8"), + networkId = MockNetworks.network3.id, + name = "Token 8", + symbol = "T8", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = false, + ) + val token9 get() = Token( + id = Token.ID("token9"), + networkId = MockNetworks.network3.id, + name = "Token 9", + symbol = "T9", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = false, + ) + val token10 get() = Token( + id = Token.ID("token10"), + networkId = MockNetworks.network3.id, + name = "Token 10", + symbol = "T10", + isCustom = false, + decimals = 8, + iconUrl = null, + isCoin = false, + ) + + val tokens get() = mapOf( + UserWalletId(stringValue = "123") to setOf( + token1, token2, token3, token4, token5, + token6, token7, token8, token9, token10, + ), + UserWalletId(stringValue = "321") to setOf(token1, token2, token3), + UserWalletId(stringValue = "42") to setOf(token7, token8, token9, token10), + UserWalletId(stringValue = "24") to setOf(token4, token5, token6), + ) + + val isGrouped get() = mapOf( + UserWalletId(stringValue = "123") to true, + UserWalletId(stringValue = "321") to false, + UserWalletId(stringValue = "42") to false, + UserWalletId(stringValue = "24") to true, + ) + + val isSortedByBalance get() = mapOf( + UserWalletId(stringValue = "123") to true, + UserWalletId(stringValue = "321") to false, + UserWalletId(stringValue = "42") to true, + UserWalletId(stringValue = "24") to false, + ) +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt similarity index 60% rename from data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt rename to data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt index dbad76f32b..a328f0a544 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt @@ -1,6 +1,8 @@ package com.tangem.data.tokens.repository import arrow.core.Either +import arrow.core.right +import com.tangem.data.tokens.mock.MockNetworks import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus @@ -8,11 +10,15 @@ import com.tangem.domain.tokens.model.Token import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf -internal class DefaultNetworksRepository : NetworksRepository { +internal class MockNetworksRepository : NetworksRepository { override fun getNetworks(networksIds: Set): Either> { - TODO("Not yet implemented") + return MockNetworks.networks + .filter { it.id in networksIds } + .toSet() + .right() } override fun getNetworkStatuses( @@ -20,6 +26,11 @@ internal class DefaultNetworksRepository : NetworksRepository { networks: Map>, refresh: Boolean, ): Flow>> { - TODO("Not yet implemented") + return flowOf( + MockNetworks.networksStatuses + .filter { it.networkId in networks.keys } + .toSet() + .right(), + ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt similarity index 56% rename from data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt rename to data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt index 087f265e96..1e6cbaa1d8 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt @@ -1,15 +1,23 @@ package com.tangem.data.tokens.repository import arrow.core.Either +import arrow.core.right +import com.tangem.data.tokens.mock.MockQuotes import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Token import com.tangem.domain.tokens.repository.QuotesRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf -internal class DefaultQuotesRepository : QuotesRepository { +internal class MockQuotesRepository : QuotesRepository { override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow>> { - TODO("Not yet implemented") + return flowOf( + MockQuotes.quotes + .filter { it.tokenId in tokensIds } + .toSet() + .right(), + ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt similarity index 60% rename from data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensRepository.kt rename to data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt index cb7575e7fa..92092f1046 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultTokensRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt @@ -1,32 +1,34 @@ package com.tangem.data.tokens.repository import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.data.tokens.mock.MockTokens import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Token import com.tangem.domain.tokens.repository.TokensRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf -internal class DefaultTokensRepository : TokensRepository { +internal class MockTokensRepository : TokensRepository { override suspend fun sortTokens( userWalletId: UserWalletId, sortedTokensIds: Set, isGrouped: Boolean, isSortedByBalance: Boolean, - ): Either { - TODO("Not yet implemented") - } + ): Either = Unit.right() override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> { - TODO("Not yet implemented") + return flowOf(value = MockTokens.tokens[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) } override fun isTokensGrouped(userWalletId: UserWalletId): Flow> { - TODO("Not yet implemented") + return flowOf(MockTokens.isGrouped[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) } override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> { - TODO("Not yet implemented") + return flowOf(MockTokens.isSortedByBalance[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) } } \ No newline at end of file From d192fe840582674fdb6f545489733f03af0fad8f Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Jul 2023 08:46:16 +0300 Subject: [PATCH 37/58] Updated on 2026-08-14 --- .../wallet2/ui/components/OnboardingDescriptionBlock.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt index 27a76c7f38..4134269aaa 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.sp import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.presentation.wallet2.model.DescriptionResource @@ -62,9 +63,13 @@ fun DescriptionTitleText(text: String) { @Composable fun DescriptionSubTitleText(text: String) { + // the text style made similar to TextViewOnboarding.Body Text( text = text, - style = TangemTheme.typography.subtitle1, + style = TangemTheme.typography.body1.copy( + lineHeight = 20.sp, + letterSpacing = 0.03.sp, + ), color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(), From 0e1caf7c0370ec1560dafcd8552514d9cda35c58 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 12 Jul 2023 15:21:34 +0300 Subject: [PATCH 38/58] Updated on 2026-08-14 --- .../onboarding/api/OnboardingSeedPhrase.kt | 1 - .../domain/DefaultSeedPhraseInteractor.kt | 2 +- .../wallet2/viewmodel/SeedPhraseErrorConverter.kt | 2 +- .../wallet2/viewmodel/SeedPhraseViewModel.kt | 15 +++------------ 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhrase.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhrase.kt index 2db7541c5a..2f6b1d4a0c 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhrase.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/api/OnboardingSeedPhrase.kt @@ -54,7 +54,6 @@ private fun Content(screen: SeedPhraseScreen, uiState: OnboardingSeedPhraseState state = uiState.introState, ) } - SeedPhraseScreen.AboutSeedPhrase -> { AboutSeedPhraseScreen( state = uiState.aboutState, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt index d0cd349b55..f3152c931e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt @@ -96,7 +96,7 @@ internal class DefaultSeedPhraseInteractor constructor( } private fun MnemonicErrorResult.mapToError(): SeedPhraseError = when (this) { - MnemonicErrorResult.InvalidWordCount -> SeedPhraseError.InvalidEntropyLength + MnemonicErrorResult.InvalidWordCount -> SeedPhraseError.InvalidWordCount MnemonicErrorResult.InvalidEntropyLength -> SeedPhraseError.InvalidEntropyLength MnemonicErrorResult.InvalidWordsFile -> SeedPhraseError.InvalidWordsFile MnemonicErrorResult.InvalidChecksum -> SeedPhraseError.InvalidChecksum diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt index dbb553ba63..9a44793b6f 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt @@ -14,7 +14,7 @@ class SeedPhraseErrorConverter : ModuleMessageConverter { + SeedPhraseError.InvalidChecksum -> { context.getString(R.string.onboarding_seed_mnemonic_invalid_checksum) } is SeedPhraseError.InvalidWords -> { diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index b5d2b6a8d7..2e6e7587eb 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -25,7 +25,6 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject /** @@ -67,7 +66,6 @@ class SeedPhraseViewModel @Inject constructor( ) private val textFieldsDebouncers = mutableMapOf() - private var suggestionWordInserted: AtomicBoolean = AtomicBoolean(false) private var generatedMnemonicComponents: List? = null private var importedMnemonicComponents: List? = null @@ -197,19 +195,14 @@ class SeedPhraseViewModel @Inject constructor( val debouncer = createOrGetDebouncer(MNEMONIC_DEBOUNCER) when { - suggestionWordInserted.getAndSet(false) -> { - debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.single) { - validateMnemonic(inputMnemonic) - } - } - isSameText && !isCursorMoved -> { - // do nothing - } isSameText && isCursorMoved -> { debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.single) { updateSuggestions(fieldState) } } + isSameText && !isCursorMoved -> { + // do nothing + } else -> { debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.single) { updateSuggestions(fieldState) @@ -243,7 +236,6 @@ class SeedPhraseViewModel @Inject constructor( is SeedPhraseError.InvalidWords -> { uiBuilder.importSeedPhrase.updateInvalidWords(uiState, error.words) } - else -> uiState } updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, error) } @@ -366,7 +358,6 @@ class SeedPhraseViewModel @Inject constructor( private fun buttonSuggestedPhraseClick(suggestionIndex: Int) { viewModelScope.launchSingle { - suggestionWordInserted.set(true) val textFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue val word = uiState.importSeedPhraseState.suggestionsList[suggestionIndex] val cursorPosition = textFieldValue.selection.end From bde99e272b94fdce814567fd29cc2a711607c9e8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 14 Jul 2023 16:28:43 +0800 Subject: [PATCH 39/58] Updated on 2026-08-14 --- app/build.gradle.kts | 3 +- .../main/java/com/tangem/tap/MainActivity.kt | 66 ++++++++++--------- .../AccessCodeRequestPolicyMiddleware.kt | 8 +-- .../java/com/tangem/tap/di/ActivityModule.kt | 41 ++++++------ .../com/tangem/tap/domain/TangemSdkManager.kt | 66 +++++-------------- .../walletconnect/WalletConnectSdkHelper.kt | 7 +- .../details/redux/DetailsMiddleware.kt | 12 ++-- .../tap/features/home/redux/HomeMiddleware.kt | 9 ++- .../redux/OnboardingWalletMiddleware.kt | 14 ++-- .../saveWallet/redux/SaveWalletMiddleware.kt | 8 ++- .../send/redux/middlewares/SendMiddleware.kt | 4 +- .../middlewares/TradeCryptoMiddleware.kt | 2 +- .../redux/WalletSelectorMiddleware.kt | 12 ++-- .../welcome/redux/WelcomeMiddleware.kt | 4 +- .../CurrencyExchangeManager.kt | 10 +-- .../com/tangem/tap/proxy/AppStateHolder.kt | 2 - .../tap/proxy/TransactionManagerImpl.kt | 5 +- .../com/tangem/tap/proxy/di/ProxyModule.kt | 8 ++- .../tap/proxy/redux/DaggerGraphAction.kt | 2 + .../tap/proxy/redux/DaggerGraphReducer.kt | 1 + .../tap/proxy/redux/DaggerGraphState.kt | 2 + data/card/.gitignore | 1 + data/card/build.gradle.kts | 21 ++++++ .../card/DefaultCardSdkConfigRepository.kt | 53 +++++++++++++++ .../com/tangem/data/card/di/CardDataModule.kt | 21 ++++++ .../com/tangem/data/card/di/CardSdkModule.kt | 23 +++++++ .../data/card/sdk/CardSdkLifecycleObserver.kt | 17 +++++ .../tangem/data/card/sdk/CardSdkProvider.kt | 14 ++++ .../data/card/sdk/DefaultCardSdkProvider.kt | 45 +++++++++++++ .../com/tangem/domain/card/ScanCardUseCase.kt | 58 +++++----------- .../repository/CardSdkConfigRepository.kt | 28 ++++++++ settings.gradle.kts | 1 + 32 files changed, 384 insertions(+), 184 deletions(-) create mode 100644 data/card/.gitignore create mode 100644 data/card/build.gradle.kts create mode 100644 data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt create mode 100644 data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt create mode 100644 data/card/src/main/java/com/tangem/data/card/di/CardSdkModule.kt create mode 100644 data/card/src/main/java/com/tangem/data/card/sdk/CardSdkLifecycleObserver.kt create mode 100644 data/card/src/main/java/com/tangem/data/card/sdk/CardSdkProvider.kt create mode 100644 data/card/src/main/java/com/tangem/data/card/sdk/DefaultCardSdkProvider.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 07f7cd8b81..75db227f9d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,7 +25,7 @@ dependencies { implementation(project(":domain:legacy")) implementation(project(":domain:models")) implementation(project(":domain:core")) - implementation(project(":domain:card")) + implementation(projects.domain.card) implementation(project(":domain:wallets")) implementation(project(":domain:wallets:models")) implementation(projects.domain.tokens) @@ -41,6 +41,7 @@ dependencies { implementation(project(":libs:crypto")) implementation(project(":libs:auth")) implementation(project(":data:source:preferences")) + implementation(projects.data.card) implementation(projects.data.tokens) /** Features */ diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 2eb8371482..1c4395d748 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -10,10 +10,11 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.Snackbar -import com.tangem.TangemSdk import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.data.card.sdk.CardSdkLifecycleObserver import com.tangem.domain.card.ScanCardUseCase +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter @@ -56,7 +57,6 @@ import java.lang.ref.WeakReference import javax.inject.Inject import kotlin.coroutines.CoroutineContext -lateinit var tangemSdk: TangemSdk lateinit var tangemSdkManager: TangemSdkManager lateinit var backupService: BackupService internal var lockUserWalletsTimer: LockUserWalletsTimer? = null @@ -88,7 +88,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac lateinit var testerRouter: TesterRouter @Inject - lateinit var injectedTangemSdk: TangemSdk + lateinit var cardSdkLifecycleObserver: CardSdkLifecycleObserver + + @Inject + lateinit var cardSdkConfigRepository: CardSdkConfigRepository @Inject lateinit var injectedTangemSdkManager: TangemSdkManager @@ -122,11 +125,11 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac systemActions() store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) - tangemSdk = injectedTangemSdk + cardSdkLifecycleObserver.onCreate(context = this) + tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager - appStateHolder.tangemSdk = tangemSdk - backupService = BackupService.init(tangemSdk, this) + backupService = BackupService.init(cardSdkConfigRepository.sdk, this) lockUserWalletsTimer = LockUserWalletsTimer(owner = this) initUserWalletsListManager() @@ -144,10 +147,37 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac walletRouter = walletRouter, walletConnectInteractor = walletConnectInteractor, tokenDetailsRouter = tokenDetailsRouter, + cardSdkConfigRepository = cardSdkConfigRepository, ), ) } + override fun onStart() { + super.onStart() + dialogManager.onStart(this) + } + + override fun onResume() { + super.onResume() + // TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop + notificationsHandler = NotificationsHandler(binding.fragmentContainer) + + navigateToInitialScreenIfNeededOnResume(intent) + } + + override fun onStop() { + notificationsHandler = null + dialogManager.onStop() + super.onStop() + } + + override fun onDestroy() { + store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this))) + intentProcessor.removeAll() + cardSdkLifecycleObserver.onDestroy() + super.onDestroy() + } + private fun initIntentHandlers() { val hasSavedWalletsProvider = { store.state.globalState.userWalletsListManager?.hasUserWallets == true } intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider)) @@ -183,13 +213,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } - override fun onResume() { - super.onResume() - notificationsHandler = NotificationsHandler(binding.fragmentContainer) - - navigateToInitialScreenIfNeededOnResume(intent) - } - override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) scope.launch { @@ -197,23 +220,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } } - override fun onStart() { - super.onStart() - dialogManager.onStart(this) - } - - override fun onStop() { - notificationsHandler = null - dialogManager.onStop() - super.onStop() - } - - override fun onDestroy() { - store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this))) - intentProcessor.removeAll() - super.onDestroy() - } - override fun showSnackbar(text: Int, buttonTitle: Int?, action: View.OnClickListener?) { if (snackbar != null) return 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 index a96f1c1e06..c385b121ae 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt @@ -3,7 +3,8 @@ package com.tangem.tap.common.redux import com.tangem.domain.models.scan.ScanResponse import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.preferencesStorage -import com.tangem.tap.tangemSdkManager +import com.tangem.tap.proxy.redux.DaggerGraphState +import com.tangem.tap.store import org.rekotlin.Middleware class AccessCodeRequestPolicyMiddleware { @@ -19,9 +20,8 @@ class AccessCodeRequestPolicyMiddleware { } private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes && - scanResponse.card.isAccessCodeSet, + store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes && scanResponse.card.isAccessCodeSet, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 96639f64e4..a894d7c4fe 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -1,42 +1,43 @@ package com.tangem.tap.di import android.content.Context -import androidx.fragment.app.FragmentActivity -import com.tangem.TangemSdk import com.tangem.domain.card.ScanCardUseCase -import com.tangem.sdk.extensions.initWithBiometrics +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.userTokensRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ActivityComponent -import dagger.hilt.android.qualifiers.ActivityContext -import dagger.hilt.android.scopes.ActivityScoped +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton @Module -@InstallIn(ActivityComponent::class) +@InstallIn(SingletonComponent::class) internal object ActivityModule { @Provides - @ActivityScoped - fun provideTangemSdk(@ActivityContext context: Context): TangemSdk { - return TangemSdk.initWithBiometrics(context as FragmentActivity, TangemSdkManager.config) + @Singleton + fun provideTangemSdkManager( + @ApplicationContext context: Context, + cardSdkConfigRepository: CardSdkConfigRepository, + ): TangemSdkManager { + return TangemSdkManager(cardSdkConfigRepository = cardSdkConfigRepository, resources = context.resources) } @Provides - @ActivityScoped - fun provideTangemSdkManager(@ActivityContext context: Context, tangemSdk: TangemSdk): TangemSdkManager { - return TangemSdkManager(tangemSdk, context) - } - - @Provides - @ActivityScoped - fun provideScanCardUseCase(tangemSdk: TangemSdk, tangemSdkManager: TangemSdkManager): ScanCardUseCase { + @Singleton + fun provideScanCardUseCase( + tangemSdkManager: TangemSdkManager, + cardSdkConfigRepository: CardSdkConfigRepository, + ): ScanCardUseCase { return ScanCardUseCase( - tangemSdk = tangemSdk, - scanCardRepository = DefaultScanCardRepository(userTokensRepository, tangemSdkManager), + cardSdkConfigRepository = cardSdkConfigRepository, + scanCardRepository = DefaultScanCardRepository( + userTokensRepository = userTokensRepository, + tangemSdkManager = tangemSdkManager, + ), ) } } \ No newline at end of file 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 c395569c5c..2ac23842a6 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -1,27 +1,25 @@ package com.tangem.tap.domain -import android.content.Context +import android.content.res.Resources import androidx.annotation.StringRes import com.tangem.Message import com.tangem.TangemSdk import com.tangem.blockchain.common.Blockchain import com.tangem.common.* import com.tangem.common.biometric.BiometricManager -import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.* import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.usersCode.UserCodeRepository import com.tangem.core.analytics.Analytics import com.tangem.crypto.bip39.DefaultMnemonic import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DerivationTaskResponse 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.operations.usersetttings.SetUserCodeRecoveryAllowedTask import com.tangem.tap.common.analytics.events.Basic @@ -38,7 +36,10 @@ import kotlinx.coroutines.withContext import kotlin.coroutines.resume @Suppress("TooManyFunctions") -class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { +class TangemSdkManager(private val cardSdkConfigRepository: CardSdkConfigRepository, private val resources: Resources) { + + private val tangemSdk: TangemSdk + get() = cardSdkConfigRepository.sdk private val userCodeRepository by lazy { UserCodeRepository( @@ -63,7 +64,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co messageRes: Int? = null, allowsRequestAccessCodeFromRepository: Boolean = false, ): CompletionResult { - val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) + val message = Message(resources.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain( runnable = ScanProductTask( card = null, @@ -80,7 +81,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsync( CreateProductWalletTask(scanResponse.cardTypesResolver), scanResponse.card.cardId, - Message(context.getString(R.string.initial_message_create_wallet_body)), + Message(resources.getString(R.string.initial_message_create_wallet_body)), ) } @@ -92,7 +93,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co is CompletionResult.Success -> runTaskAsync( CreateProductWalletTask(scanResponse.cardTypesResolver, seedResult.data), scanResponse.card.cardId, - Message(context.getString(R.string.initial_message_create_wallet_body)), + Message(resources.getString(R.string.initial_message_create_wallet_body)), ) is CompletionResult.Failure -> CompletionResult.Failure(seedResult.error) @@ -111,7 +112,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain( CreateWalletAndRescanTask(), cardId, - initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)), + initialMessage = Message(resources.getString(R.string.initial_message_create_wallet_body)), ) .map { CardDTO(it) } } @@ -127,7 +128,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain( runnable = ResetToFactorySettingsTask(), cardId = cardId, - initialMessage = Message(context.getString(R.string.card_settings_reset_card_to_factory)), + initialMessage = Message(resources.getString(R.string.card_settings_reset_card_to_factory)), ) .map { CardDTO(it) } } @@ -154,7 +155,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain( SetUserCodeCommand.changePasscode(null), cardId, - initialMessage = Message(context.getString(R.string.initial_message_change_passcode_body)), + initialMessage = Message(resources.getString(R.string.initial_message_change_passcode_body)), ) } @@ -162,7 +163,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain( SetUserCodeCommand.changeAccessCode(null), cardId, - initialMessage = Message(context.getString(R.string.initial_message_change_access_code_body)), + initialMessage = Message(resources.getString(R.string.initial_message_change_access_code_body)), ) } @@ -170,15 +171,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain( SetUserCodeCommand.resetUserCodes(), cardId, - initialMessage = Message(context.getString(R.string.initial_message_tap_header)), - ) - } - - suspend fun checkUserCodes(cardId: String?): CompletionResult { - return runTaskAsyncReturnOnMain( - CheckUserCodesCommand(), - cardId, - initialMessage = Message(context.getString(R.string.initial_message_tap_header)), + initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), ) } @@ -186,7 +179,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain( SetUserCodeRecoveryAllowedTask(enabled), cardId, - initialMessage = Message(context.getString(R.string.initial_message_tap_header)), + initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), ) } @@ -197,7 +190,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co return runTaskAsyncReturnOnMain( runnable = ScanTask(allowRequestAccessCodeFromRepository), cardId = cardId, - initialMessage = Message(context.getString(R.string.initial_message_tap_header)), + initialMessage = Message(resources.getString(R.string.initial_message_tap_header)), ) .map { CardDTO(it) } } @@ -233,31 +226,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co } } + @Deprecated("TangemSdkManager shouldn't returns a string from resources") fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String { - return context.getString(stringResId, *formatArgs) - } - - fun setAccessCodeRequestPolicy(useBiometricsForAccessCode: Boolean) { - tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) { - UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode) - } else { - UserCodeRequestPolicy.Default - } - } - - fun useBiometricsForAccessCode(): Boolean { - val policy = tangemSdk.config.userCodeRequestPolicy - return policy is UserCodeRequestPolicy.AlwaysWithBiometrics && policy.codeType == UserCodeType.AccessCode - } - - companion object { - val config = Config( - linkedTerminal = true, - allowUntrustedCards = true, - filter = CardFilter( - allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(), - maxFirmwareVersion = FirmwareVersion(major = 4, minor = 52), - ), - ) + return resources.getString(stringResId, *formatArgs) } } \ 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 6466ac2f85..03f8cf7d40 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 @@ -36,8 +36,8 @@ import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTrans import com.tangem.tap.features.details.redux.walletconnect.* import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store -import com.tangem.tap.tangemSdk import com.tangem.tap.tangemSdkManager import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage.WCSignType.* import timber.log.Timber @@ -163,7 +163,10 @@ class WalletConnectSdkHelper { private suspend fun sendTransaction(data: WcTransactionData, cardId: String?): String? { val result = (data.walletManager as TransactionSender).send( transactionData = data.transaction, - signer = CommonSigner(tangemSdk, cardId), + signer = CommonSigner( + tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk, + cardId = cardId, + ), ) return when (result) { SimpleResult.Success -> { 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 b93e243322..e665b7cee3 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 @@ -372,9 +372,9 @@ class DetailsMiddleware { Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On)) preferencesStorage.shouldSaveAccessCodes = true - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = scanResponse?.card?.isAccessCodeSet == true, - ) + store.state.daggerGraphState + .get(DaggerGraphState::cardSdkConfigRepository) + .setAccessCodeRequestPolicy(isBiometricsRequestPolicy = scanResponse?.card?.isAccessCodeSet == true) return CompletionResult.Success(Unit) } @@ -385,9 +385,9 @@ class DetailsMiddleware { Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.Off)) preferencesStorage.shouldSaveAccessCodes = false - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = false, - ) + store.state.daggerGraphState + .get(DaggerGraphState::cardSdkConfigRepository) + .setAccessCodeRequestPolicy(isBiometricsRequestPolicy = false) } .doOnFailure { error -> Timber.e(error, "Unable to delete saved access codes") 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 01e2d50c0d..547440e96f 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 @@ -9,7 +9,6 @@ import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.models.scan.ScanResponse -import com.tangem.tap.* import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop @@ -26,7 +25,11 @@ import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.home.redux.HomeMiddleware.BUY_WALLET_URL import com.tangem.tap.features.send.redux.states.ButtonState import com.tangem.tap.features.signin.redux.SignInAction +import com.tangem.tap.preferencesStorage import com.tangem.tap.proxy.redux.DaggerGraphState +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 @@ -74,8 +77,8 @@ private fun handleHomeAction(action: Action) { private fun readCard(analyticsEvent: AnalyticsEvent?) = scope.launch { delay(timeMillis = 200) - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, ) store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan( 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 67921cce64..fe5800266a 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 @@ -30,9 +30,9 @@ import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.models.toCurrencies import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.launch import org.rekotlin.Action -import org.rekotlin.DispatchFunction import org.rekotlin.Middleware object OnboardingWalletMiddleware { @@ -43,8 +43,8 @@ private val onboardingWalletMiddleware: Middleware = { dispatch, state { next -> { action -> when (action) { - is OnboardingWallet2Action -> handleWallet2Action(action, state) - else -> handleWalletAction(action, state, dispatch) + is OnboardingWallet2Action -> handleWallet2Action(action) + else -> handleWalletAction(action) } next(action) } @@ -52,7 +52,7 @@ private val onboardingWalletMiddleware: Middleware = { dispatch, state } @Suppress("LongMethod", "ComplexMethod") -private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: DispatchFunction) { +private fun handleWalletAction(action: Action) { if (action !is OnboardingWalletAction) return val globalState = store.state.globalState @@ -178,7 +178,7 @@ private fun handleWalletAction(action: Action, state: () -> AppState?, dispatch: } @Suppress("LongMethod", "ComplexMethod") -private fun handleWallet2Action(action: OnboardingWallet2Action, state: () -> AppState?) { +private fun handleWallet2Action(action: OnboardingWallet2Action) { val globalState = store.state.globalState val onboardingManager = globalState.onboardingState.onboardingManager @@ -329,7 +329,9 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) is BackupAction.AddBackupCard -> { backupService.addBackupCard { result -> backupService.skipCompatibilityChecks = false - tangemSdk.config.filter.cardIdFilter = null + store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk + .config.filter.cardIdFilter = null + when (result) { is CompletionResult.Success -> { store.dispatchOnMain(BackupAction.AddBackupCard.Success) 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 945fc90afd..370e759dff 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 @@ -22,6 +22,7 @@ import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.isLockable import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.launch import org.rekotlin.Middleware import timber.log.Timber @@ -110,9 +111,10 @@ internal class SaveWalletMiddleware { // Enable saving access codes only if this is the first time user save the wallet if (isFirstSavedWallet) { preferencesStorage.shouldSaveAccessCodes = true - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = userWallet.hasAccessCode, - ) + store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository) + .setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = userWallet.hasAccessCode, + ) } val savedUserWallet = userWalletsListManager.selectedUserWalletSync.guard { 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 69891195d6..a76724c7c3 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 @@ -7,7 +7,6 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras import com.tangem.blockchain.blockchains.ton.TonTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder.XrpTransactionExtras -import com.tangem.core.navigation.NavigationAction import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.extensions.SimpleResult @@ -15,6 +14,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics +import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.models.scan.CardDTO @@ -39,6 +39,7 @@ import com.tangem.tap.features.send.redux.FeeAction.RequestFee import com.tangem.tap.features.send.redux.states.* import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -201,6 +202,7 @@ private fun sendTransaction( return@launch } + val tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk val linkedTerminalState = tangemSdk.config.linkedTerminal if (card.isStart2Coin) { tangemSdk.config.linkedTerminal = false 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 fbb76f391a..65be537ac8 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 @@ -72,7 +72,7 @@ class TradeCryptoMiddleware { } scope.launch { - exchangeManager.buyErc20TestnetTokens( + buyErc20TestnetTokens( card = card, walletManager = walletManager, token = currency.token, 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 148e95d323..a331a52719 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 @@ -126,11 +126,13 @@ internal class WalletSelectorMiddleware { private fun addWallet() = scope.launch { Analytics.send(MyWallets.Button.ScanNewCard()) - val prevUseBiometricsForAccessCode = tangemSdkManager.useBiometricsForAccessCode() + val cardSdkConfigRepository = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository) + + val prevUseBiometricsForAccessCode = cardSdkConfigRepository.isBiometricsRequestPolicy() // Update access code policy for access code saving when a card was scanned - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + cardSdkConfigRepository.setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, ) store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan( @@ -147,14 +149,14 @@ internal class WalletSelectorMiddleware { saveUserWalletAndPopBackToWalletScreen(scanResponse) .doOnFailure { error -> // Rollback policy if card saving was failed - tangemSdkManager.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) + cardSdkConfigRepository.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) + cardSdkConfigRepository.setAccessCodeRequestPolicy(prevUseBiometricsForAccessCode) Timber.e(error, "Unable to scan card") store.dispatchOnMain(WalletSelectorAction.AddWallet.Error(error)) }, 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 7fab7c2a4f..c10e1bcad2 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 @@ -100,8 +100,8 @@ internal class WelcomeMiddleware { } private suspend inline fun scanCardInternal(crossinline onCardScanned: suspend (ScanResponse) -> Unit) { - tangemSdkManager.setAccessCodeRequestPolicy( - useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes, + store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = preferencesStorage.shouldSaveAccessCodes, ) store.state.daggerGraphState.get(DaggerGraphState::scanCardProcessor).scan( analyticsEvent = Basic.CardWasScanned(AnalyticsParam.ScannedFrom.SignIn), 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 d886b5873f..ae3e244978 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 @@ -13,8 +13,8 @@ import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner import com.tangem.tap.features.wallet.models.Currency +import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.store -import com.tangem.tap.tangemSdk import java.math.BigDecimal /** @@ -86,11 +86,7 @@ class CurrencyExchangeManager( } } -suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( - card: CardDTO, - walletManager: EthereumWalletManager, - token: Token, -) { +suspend fun buyErc20TestnetTokens(card: CardDTO, walletManager: EthereumWalletManager, token: Token) { walletManager.safeUpdate() val amountToSend = Amount(walletManager.wallet.blockchain) @@ -109,7 +105,7 @@ suspend fun CurrencyExchangeManager.buyErc20TestnetTokens( val signer = TangemSigner( card = card, - tangemSdk = tangemSdk, + tangemSdk = store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository).sdk, initialMessage = Message(), ) { signResponse -> store.dispatch( 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 cf6015d446..7533a00f58 100644 --- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt +++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt @@ -1,6 +1,5 @@ package com.tangem.tap.proxy -import com.tangem.TangemSdk import com.tangem.core.navigation.NavigationAction import com.tangem.core.navigation.NavigationStateHolder import com.tangem.domain.models.scan.CardDTO @@ -30,7 +29,6 @@ class AppStateHolder @Inject constructor() : WalletsStateHolder, NavigationState var userTokensRepository: UserTokensRepository? = null var mainStore: Store? = null var tangemSdkManager: TangemSdkManager? = null - var tangemSdk: TangemSdk? = null var walletStoresManager: WalletStoresManager? = null var appFiatCurrency: FiatCurrency = FiatCurrency.Default 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 fe86122123..7c3e6f8b1e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -14,6 +14,7 @@ import com.tangem.blockchain.extensions.isNetworkError import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.common.BlockchainNetwork import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.lib.crypto.TransactionManager @@ -24,7 +25,6 @@ import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.Basic.TransactionSent.MemoType import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner -import com.tangem.tap.tangemSdk import java.math.BigDecimal import java.math.BigInteger import java.math.MathContext @@ -34,6 +34,7 @@ import java.math.RoundingMode class TransactionManagerImpl( private val appStateHolder: AppStateHolder, private val analytics: AnalyticsEventHandler, + private val cardSdkConfigRepository: CardSdkConfigRepository, ) : TransactionManager { override suspend fun sendApproveTransaction( @@ -395,7 +396,7 @@ class TransactionManagerImpl( val actualCard = requireNotNull(appStateHolder.getActualCard()) { "no card found" } return TangemSigner( card = actualCard, - tangemSdk = tangemSdk, + tangemSdk = cardSdkConfigRepository.sdk, initialMessage = Message(), ) { signResponse -> appStateHolder.mainStore?.dispatch( 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 371f1e21bf..cc2e04969d 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,6 +2,7 @@ package com.tangem.tap.proxy.di import androidx.compose.ui.text.intl.Locale import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.feature.learn2earn.domain.api.Learn2earnDependencyProvider import com.tangem.lib.crypto.DerivationManager import com.tangem.lib.crypto.TransactionManager @@ -39,8 +40,13 @@ class ProxyModule { fun provideTransactionManager( appStateHolder: AppStateHolder, analytics: AnalyticsEventHandler, + cardSdkConfigRepository: CardSdkConfigRepository, ): TransactionManager { - return TransactionManagerImpl(appStateHolder, analytics) + return TransactionManagerImpl( + appStateHolder = appStateHolder, + analytics = analytics, + cardSdkConfigRepository = cardSdkConfigRepository, + ) } @Provides diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt index e83ed33f3a..1b0f19c9fe 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt @@ -1,6 +1,7 @@ package com.tangem.tap.proxy.redux import com.tangem.domain.card.ScanCardUseCase +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import com.tangem.features.wallet.navigation.WalletRouter @@ -15,5 +16,6 @@ sealed interface DaggerGraphAction : Action { val walletRouter: WalletRouter, val walletConnectInteractor: WalletConnectInteractor, val tokenDetailsRouter: TokenDetailsRouter, + val cardSdkConfigRepository: CardSdkConfigRepository, ) : DaggerGraphAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt index bdd590be4d..aec162a122 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt @@ -18,6 +18,7 @@ object DaggerGraphReducer { walletRouter = action.walletRouter, walletConnectInteractor = action.walletConnectInteractor, tokenDetailsRouter = action.tokenDetailsRouter, + cardSdkConfigRepository = action.cardSdkConfigRepository, ) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 7bdabd80a3..228d3f014b 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -4,6 +4,7 @@ import com.tangem.datasource.asset.AssetReader import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.ScanCardUseCase +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles import com.tangem.features.tokendetails.navigation.TokenDetailsRouter @@ -29,6 +30,7 @@ data class DaggerGraphState( val tokenDetailsFeatureToggles: TokenDetailsFeatureToggles? = null, val tokenDetailsRouter: TokenDetailsRouter? = null, val scanCardProcessor: ScanCardProcessor? = null, + val cardSdkConfigRepository: CardSdkConfigRepository? = null, ) : StateType { inline fun get(getDependency: DaggerGraphState.() -> T?): T { diff --git a/data/card/.gitignore b/data/card/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/data/card/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/card/build.gradle.kts b/data/card/build.gradle.kts new file mode 100644 index 0000000000..69bbb44e78 --- /dev/null +++ b/data/card/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.data.card" +} + +dependencies { + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) + implementation(deps.tangem.card.android) + implementation(deps.tangem.card.core) + + implementation(projects.domain.card) + implementation(projects.domain.models) +} \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt new file mode 100644 index 0000000000..e263616c2f --- /dev/null +++ b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt @@ -0,0 +1,53 @@ +package com.tangem.data.card + +import com.tangem.TangemSdk +import com.tangem.common.UserCodeType +import com.tangem.common.core.CardIdDisplayFormat +import com.tangem.common.core.UserCodeRequestPolicy +import com.tangem.data.card.sdk.CardSdkProvider +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.models.scan.ProductType + +/** + * Implementation of repository for managing with CardSDK config + * + * @property cardSdkProvider CardSDK instance provider + * +[REDACTED_AUTHOR] + */ +internal class DefaultCardSdkConfigRepository( + private val cardSdkProvider: CardSdkProvider, +) : CardSdkConfigRepository { + + @Deprecated("Use CardSdkConfigRepository's methods instead of this property") + override val sdk: TangemSdk + get() = cardSdkProvider.sdk + + override fun setAccessCodeRequestPolicy(isBiometricsRequestPolicy: Boolean) { + sdk.config.userCodeRequestPolicy = if (isBiometricsRequestPolicy) { + UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode) + } else { + UserCodeRequestPolicy.Default + } + } + + override fun isBiometricsRequestPolicy(): Boolean { + return with(sdk.config.userCodeRequestPolicy) { + this is UserCodeRequestPolicy.AlwaysWithBiometrics && codeType == UserCodeType.AccessCode + } + } + + override fun resetCardIdDisplayFormat() { + sdk.config.cardIdDisplayFormat = CardIdDisplayFormat.Full + } + + override fun updateCardIdDisplayFormat(productType: ProductType) { + sdk.config.cardIdDisplayFormat = when (productType) { + ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4) + ProductType.Note, + ProductType.Wallet, + ProductType.Start2Coin, + -> CardIdDisplayFormat.Full + } + } +} \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt b/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt new file mode 100644 index 0000000000..4ed7adf268 --- /dev/null +++ b/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt @@ -0,0 +1,21 @@ +package com.tangem.data.card.di + +import com.tangem.data.card.DefaultCardSdkConfigRepository +import com.tangem.data.card.sdk.CardSdkProvider +import com.tangem.domain.card.repository.CardSdkConfigRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object CardDataModule { + + @Provides + @Singleton + fun provideCardSdkConfigRepository(cardSdkProvider: CardSdkProvider): CardSdkConfigRepository { + return DefaultCardSdkConfigRepository(cardSdkProvider = cardSdkProvider) + } +} \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/di/CardSdkModule.kt b/data/card/src/main/java/com/tangem/data/card/di/CardSdkModule.kt new file mode 100644 index 0000000000..c88f2dbfc2 --- /dev/null +++ b/data/card/src/main/java/com/tangem/data/card/di/CardSdkModule.kt @@ -0,0 +1,23 @@ +package com.tangem.data.card.di + +import com.tangem.data.card.sdk.CardSdkLifecycleObserver +import com.tangem.data.card.sdk.CardSdkProvider +import com.tangem.data.card.sdk.DefaultCardSdkProvider +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface CardSdkModule { + + @Binds + @Singleton + fun provideCardSdkProvider(defaultCardSdkProvider: DefaultCardSdkProvider): CardSdkProvider + + @Binds + @Singleton + fun providerCardSdkLifecycleObserver(defaultCardSdkProvider: DefaultCardSdkProvider): CardSdkLifecycleObserver +} \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkLifecycleObserver.kt b/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkLifecycleObserver.kt new file mode 100644 index 0000000000..f5f6781392 --- /dev/null +++ b/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkLifecycleObserver.kt @@ -0,0 +1,17 @@ +package com.tangem.data.card.sdk + +import android.content.Context + +/** + * Lifecycle observer for creating Card SDK instance + * +[REDACTED_AUTHOR] + */ +interface CardSdkLifecycleObserver { + + /** Callback of creating activity [context] */ + fun onCreate(context: Context) + + /** Callback of destroying activity */ + fun onDestroy() +} \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkProvider.kt b/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkProvider.kt new file mode 100644 index 0000000000..0d0b88fa41 --- /dev/null +++ b/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkProvider.kt @@ -0,0 +1,14 @@ +package com.tangem.data.card.sdk + +import com.tangem.TangemSdk + +/** + * CardSDK instance provider + * +[REDACTED_AUTHOR] + */ +internal interface CardSdkProvider { + + /** CardSDK instance */ + val sdk: TangemSdk +} \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/sdk/DefaultCardSdkProvider.kt b/data/card/src/main/java/com/tangem/data/card/sdk/DefaultCardSdkProvider.kt new file mode 100644 index 0000000000..dddff7e3da --- /dev/null +++ b/data/card/src/main/java/com/tangem/data/card/sdk/DefaultCardSdkProvider.kt @@ -0,0 +1,45 @@ +package com.tangem.data.card.sdk + +import android.content.Context +import androidx.fragment.app.FragmentActivity +import com.tangem.TangemSdk +import com.tangem.common.CardFilter +import com.tangem.common.card.FirmwareVersion +import com.tangem.common.core.Config +import com.tangem.sdk.extensions.initWithBiometrics +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Implementation of CardSDK instance provider + * +[REDACTED_AUTHOR] + */ +@Singleton +internal class DefaultCardSdkProvider @Inject constructor() : CardSdkProvider, CardSdkLifecycleObserver { + + override val sdk: TangemSdk + get() = requireNotNull(value = _sdk) { "Impossible to get the TangemSdk when activity is destroyed" } + + private var _sdk: TangemSdk? = null + + override fun onCreate(context: Context) { + _sdk = TangemSdk.initWithBiometrics(activity = context as FragmentActivity, config = config) + } + + override fun onDestroy() { + _sdk = null + } + + private companion object { + + val config = Config( + linkedTerminal = true, + allowUntrustedCards = true, + filter = CardFilter( + allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(), + maxFirmwareVersion = FirmwareVersion(major = 4, minor = 52), + ), + ) + } +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt index 46312c439f..69dbc7a617 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt @@ -4,47 +4,46 @@ import arrow.core.Either import arrow.core.EitherNel import arrow.core.flatMap import com.tangem.TangemSdk -import com.tangem.common.core.CardIdDisplayFormat +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.card.repository.ScanCardRepository import com.tangem.domain.core.chain.Chain import com.tangem.domain.core.chain.ChainProcessor -import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse /** - * Use case responsible for scanning a card and returning a [ScanResponse] object. - * @property scanCardRepository A repository object implementing [ScanCardRepository] interface. - * @property tangemSdk An instance of [TangemSdk] to configure the display format of the card ID. - * @constructor Create a new instance of [ScanCardUseCase] with the given dependencies. + * Use case responsible for scanning a card and returning a [ScanResponse] object + * + * @property scanCardRepository a repository object implementing [ScanCardRepository] interface + * @property cardSdkConfigRepository an instance of [TangemSdk] to configure the display format of the card ID + * + * @constructor create a new instance of [ScanCardUseCase] with the given dependencies */ class ScanCardUseCase( private val scanCardRepository: ScanCardRepository, - private val tangemSdk: TangemSdk, + private val cardSdkConfigRepository: CardSdkConfigRepository, ) { - /** - * A [ChainProcessor] object to launch the after-scan chains. - */ + /** A [ChainProcessor] object to launch the after-scan chains */ private val scanChainProcessor by lazy { ChainProcessor() } /** * Scan a card and return a [ScanResponse] object. - * @param cardId an optional card ID to scan. If null, can scan any card present. - * Defaults to null. - * @param allowRequestAccessCodeFromStorage whether to prompt the user for an access code if needed. - * Defaults to false. - * @param afterScanChains A list of chains that should be executed after a successful card scan operation. - * Defaults to an empty array. - * @return A [EitherNel] object with either a non-empty list of [ScanCardException] or a [ScanResponse]. + * + * @param cardId an optional card ID to scan. If null, can scan any card present + * @param allowRequestAccessCodeFromStorage whether to prompt the user for an access code if needed + * @param afterScanChains a list of chains that should be executed after a successful card scan + * operation. Defaults to an empty array + * + * @return a [EitherNel] object with either a non-empty list of [ScanCardException] or a [ScanResponse] */ suspend operator fun invoke( cardId: String? = null, allowRequestAccessCodeFromStorage: Boolean = false, afterScanChains: List> = emptyList(), ): Either { - resetCardIdDisplayFormat() + cardSdkConfigRepository.resetCardIdDisplayFormat() scanChainProcessor.addChains(afterScanChains) return scanCardRepository.scanCard( @@ -52,31 +51,10 @@ class ScanCardUseCase( allowRequestAccessCodeFromStorage = allowRequestAccessCodeFromStorage, ) .onRight { scanResponse -> - updateCardIdDisplayFormat(scanResponse.productType) + cardSdkConfigRepository.updateCardIdDisplayFormat(scanResponse.productType) } .flatMap { response -> scanChainProcessor.launchChains(initial = response) } } - - /** - * Reset the card ID display format to [CardIdDisplayFormat.Full]. - */ - private fun resetCardIdDisplayFormat() { - tangemSdk.config.cardIdDisplayFormat = CardIdDisplayFormat.Full - } - - /** - * Update the card ID display format according to the [ProductType] of the scanned card. - * @param productType The [ProductType] of the scanned card. - */ - private fun updateCardIdDisplayFormat(productType: ProductType) { - tangemSdk.config.cardIdDisplayFormat = when (productType) { - ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4) - ProductType.Note, - ProductType.Wallet, - ProductType.Start2Coin, - -> CardIdDisplayFormat.Full - } - } } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt new file mode 100644 index 0000000000..11fa7e26e1 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt @@ -0,0 +1,28 @@ +package com.tangem.domain.card.repository + +import com.tangem.TangemSdk +import com.tangem.domain.models.scan.ProductType + +/** + * Repository for managing with CardSDK config + * +[REDACTED_AUTHOR] + */ +interface CardSdkConfigRepository { + + /** Tangem SDK instance */ + @Deprecated("Use CardSdkConfigRepository's methods instead of this property") + val sdk: TangemSdk + + /** Set access code request policy by [isBiometricsRequestPolicy] */ + fun setAccessCodeRequestPolicy(isBiometricsRequestPolicy: Boolean) + + /** Check if config has biometrics request policy */ + fun isBiometricsRequestPolicy(): Boolean + + /** Reset the card ID display format to start value */ + fun resetCardIdDisplayFormat() + + /** Update the card ID display format according to the [productType] of the scanned card */ + fun updateCardIdDisplayFormat(productType: ProductType) +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 423368f3d8..4cd9f9833e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -85,6 +85,7 @@ include(":domain:tokens") // endregion Domain modules // region Data modules +include(":data:card") include(":data:tokens") include(":data:source:preferences") // endregion Data modules \ No newline at end of file From d8348e54df0cbcc2e5cb7d8fa68c7c648bf4a5bf Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Jul 2023 09:50:38 +0300 Subject: [PATCH 40/58] Updated on 2026-08-14 --- .../core/ui/utils/BigDecimalFormatter.kt | 71 ++++++++++++ .../domain/tokens/model/NetworkGroup.kt | 4 +- .../tangem/domain/tokens/model/TokenList.kt | 5 +- .../tokens/utils/TokenListOperations.kt | 2 +- .../domain/tokens/mock/MockTokenLists.kt | 4 +- features/wallet/impl/build.gradle.kts | 1 + .../presentation/common/WalletPreviewData.kt | 15 ++- .../utils/FiatBalanceToWalletCardConverter.kt | 40 +++++++ .../wallet/utils/LoadingItemsProvider.kt | 15 +++ .../utils/TokenErrorToWalletStateConverter.kt | 15 +++ .../utils/TokenListToContentItemsConverter.kt | 63 +++++++++++ .../utils/TokenListToWalletStateConverter.kt | 56 ++++++++++ .../utils/TokenStatusToTokenItemConverter.kt | 105 ++++++++++++++++++ .../wallet/viewmodels/WalletViewModel.kt | 79 +++++++++++-- 14 files changed, 448 insertions(+), 27 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenStatusToTokenItemConverter.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt new file mode 100644 index 0000000000..d10357cd54 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -0,0 +1,71 @@ +package com.tangem.core.ui.utils + +import java.math.BigDecimal +import java.math.RoundingMode +import java.text.NumberFormat +import java.util.Currency +import java.util.Locale + +object BigDecimalFormatter { + + private const val TEMP_CURRENCY_CODE = "USD" + + fun formatCryptoAmount( + cryptoAmount: BigDecimal, + cryptoCurrency: String, + decimals: Int, + locale: Locale = Locale.getDefault(), + ): String { + val formatterCurrency = getCurrency(cryptoCurrency) + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = formatterCurrency + maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8) + minimumFractionDigits = 2 + roundingMode = RoundingMode.DOWN + } + + return formatter.format(cryptoAmount) + .replace(formatterCurrency.getSymbol(locale), cryptoCurrency) + } + + fun formatFiatAmount( + fiatAmount: BigDecimal, + fiatCurrencyCode: String, + fiatCurrencySymbol: String, + locale: Locale = Locale.getDefault(), + ): String { + val formatterCurrency = getCurrency(fiatCurrencyCode) + val formatter = NumberFormat.getCurrencyInstance(locale).apply { + currency = formatterCurrency + maximumFractionDigits = 2 + minimumFractionDigits = 2 + roundingMode = RoundingMode.HALF_UP + } + + return formatter.format(fiatAmount) + .replace(formatterCurrency.getSymbol(locale), fiatCurrencySymbol) + } + + fun formatPercent(percent: BigDecimal, useAbsoluteValue: Boolean, locale: Locale = Locale.getDefault()): String { + val formatter = NumberFormat.getPercentInstance(locale).apply { + maximumFractionDigits = 2 + minimumFractionDigits = 2 + roundingMode = RoundingMode.HALF_UP + } + val value = if (useAbsoluteValue) percent.abs() else percent + + return formatter.format(value) + } + + private fun getCurrency(code: String): Currency { + return runCatching { Currency.getInstance(code) } + .getOrElse { e -> + // Currency code is not valid ISO 4217 code + if (e is IllegalArgumentException) { + Currency.getInstance(TEMP_CURRENCY_CODE) + } else { + throw e + } + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt index ea33623175..acc05ff374 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt @@ -1,9 +1,7 @@ package com.tangem.domain.tokens.model -import arrow.core.NonEmptySet - data class NetworkGroup( val networkId: Network.ID, val name: String, - val tokens: NonEmptySet, + val tokens: Set, ) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt index e94a89de44..3f1ffaaf75 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/TokenList.kt @@ -1,6 +1,5 @@ package com.tangem.domain.tokens.model -import arrow.core.NonEmptySet import java.math.BigDecimal sealed class TokenList { @@ -8,13 +7,13 @@ sealed class TokenList { open val sortedBy: SortType = SortType.NONE data class GroupedByNetwork( - val groups: NonEmptySet, + val groups: Set, override val totalFiatBalance: FiatBalance, override val sortedBy: SortType, ) : TokenList() data class Ungrouped( - val tokens: NonEmptySet, + val tokens: Set, override val totalFiatBalance: FiatBalance, override val sortedBy: SortType, ) : TokenList() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt index 01a126b942..f5d8bf0d5c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt @@ -84,7 +84,7 @@ internal class TokenListOperations( ): NonEmptySet { val groupsWithSortedTokens = groupTokens(tokens, networks) .map { group -> - group.copy(tokens = sortTokensByBalance(group.tokens)) + group.copy(tokens = sortTokensByBalance(group.tokens as NonEmptySet)) } .toNonEmptySet() val sortedGroups = if (isAnyTokenLoading) { diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt index 78b4810d7b..c705c614f2 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt @@ -68,7 +68,7 @@ internal object MockTokenLists { val loadingUngroupedTokenList = with(ungroupedTokenList) { copy( - tokens = tokens.map { it.copy(value = TokenStatus.Loading) }.toNonEmptySet(), + tokens = tokens.map { it.copy(value = TokenStatus.Loading) }.toSet(), totalFiatBalance = TokenList.FiatBalance.Loading, ) } @@ -91,7 +91,7 @@ internal object MockTokenLists { val sortedGroupedTokenList: TokenList.GroupedByNetwork get() { - val groups = sortedNetworksGroups + val groups = sortedNetworksGroups.toSet() return groupedTokenList.copy( groups = groups, diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index c0f88c2b7e..c216409615 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(deps.kotlin.immutable.collections) implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) + implementation(deps.arrow.core) /** DI */ implementation(deps.hilt.android) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index c89bcc47ee..dab4813f30 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -4,6 +4,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.marketprice.PriceChangeConfig import com.tangem.core.ui.components.transactions.TransactionState +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem @@ -51,14 +52,16 @@ internal object WalletPreviewData { onClick = null, ) + val wallets = mapOf( + UserWalletId(stringValue = "123") to walletCardContentState, + UserWalletId(stringValue = "321") to walletCardLoadingState, + UserWalletId(stringValue = "42") to walletCardHiddenContentState, + UserWalletId(stringValue = "24") to walletCardErrorState, + ) + val walletListConfig = WalletsListConfig( selectedWalletIndex = 0, - wallets = persistentListOf( - walletCardContentState, - walletCardLoadingState, - walletCardHiddenContentState, - walletCardErrorState, - ), + wallets = wallets.values.toPersistentList(), onWalletChange = {}, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt new file mode 100644 index 0000000000..2fe69a783c --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/FiatBalanceToWalletCardConverter.kt @@ -0,0 +1,40 @@ +package com.tangem.feature.wallet.presentation.wallet.utils + +import com.tangem.core.ui.utils.BigDecimalFormatter.formatFiatAmount +import com.tangem.domain.tokens.model.TokenList +import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState +import com.tangem.utils.converter.Converter + +internal class FiatBalanceToWalletCardConverter( + private val currentState: WalletCardState, + private val isWalletContentHidden: Boolean, + private val fiatCurrencyCode: String, + private val fiatCurrencySymbol: String, +) : Converter { + + override fun convert(value: TokenList.FiatBalance): WalletCardState { + // TODO: [REDACTED_JIRA] + return when (value) { + is TokenList.FiatBalance.Loading -> with(currentState) { + WalletCardState.Loading(id, title, additionalInfo, imageResId, onClick) + } + is TokenList.FiatBalance.Failed -> with(currentState) { + WalletCardState.Error(id, title, additionalInfo, imageResId, onClick) + } + is TokenList.FiatBalance.Loaded -> with(currentState) { + if (isWalletContentHidden) { + WalletCardState.HiddenContent(id, title, additionalInfo, imageResId, onClick) + } else { + WalletCardState.Content( + id = id, + title = title, + additionalInfo = additionalInfo, + imageResId = imageResId, + onClick = onClick, + balance = formatFiatAmount(value.amount, fiatCurrencyCode, fiatCurrencySymbol), + ) + } + } + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt new file mode 100644 index 0000000000..c00f864c32 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/LoadingItemsProvider.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.wallet.presentation.wallet.utils + +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState.MultiCurrencyItem +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.toPersistentList + +internal object LoadingItemsProvider { + + fun getLoadingMultiCurrencyTokens(): PersistentList { + return List(size = 5) { TokenItemState.Loading } + .map { MultiCurrencyItem.Token(it) } + .toPersistentList() + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt new file mode 100644 index 0000000000..0f8cb0ca2f --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.wallet.presentation.wallet.utils + +import com.tangem.domain.tokens.error.TokensError +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.utils.converter.Converter + +internal class TokenErrorToWalletStateConverter( + private val currentState: WalletStateHolder, +) : Converter { + + // TODO: [REDACTED_JIRA] + override fun convert(value: TokensError): WalletStateHolder { + return currentState + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt new file mode 100644 index 0000000000..731c766675 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt @@ -0,0 +1,63 @@ +package com.tangem.feature.wallet.presentation.wallet.utils + +import com.tangem.domain.tokens.model.NetworkGroup +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState.MultiCurrencyItem +import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.mutate +import kotlinx.collections.immutable.persistentListOf + +internal class TokenListToContentItemsConverter( + isWalletContentHidden: Boolean, + fiatCurrencyCode: String, + fiatCurrencySymbol: String, +) : Converter> { + + private val tokenStatusConverter = TokenStatusToTokenItemConverter( + isWalletContentHidden, + fiatCurrencyCode, + fiatCurrencySymbol, + ) + + override fun convert(value: TokenList): ImmutableList { + return when (value) { + is TokenList.GroupedByNetwork -> value.mapToMultiCurrencyItems() + is TokenList.Ungrouped -> value.mapToMultiCurrencyItems() + is TokenList.NotInitialized -> getLoadingMultiCurrencyTokens() + } + } + + private fun TokenList.GroupedByNetwork.mapToMultiCurrencyItems(): PersistentList { + return groups.fold(initial = persistentListOf()) { acc, group -> + acc.mutate { it.addGroup(group) } + } + } + + private fun TokenList.Ungrouped.mapToMultiCurrencyItems(): PersistentList { + return tokens.fold(initial = persistentListOf()) { acc, token -> + acc.mutate { it.addToken(token) } + } + } + + private fun MutableList.addGroup(group: NetworkGroup): List { + this.add(MultiCurrencyItem.NetworkGroupTitle(group.name)) + + group.tokens.forEach { token -> + this.addToken(token) + } + + return this + } + + private fun MutableList.addToken(token: TokenStatus): List { + val tokenItemState = tokenStatusConverter.convert(token) + + this.add(MultiCurrencyItem.Token(tokenItemState)) + + return this + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt new file mode 100644 index 0000000000..af7cf9373e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt @@ -0,0 +1,56 @@ +package com.tangem.feature.wallet.presentation.wallet.utils + +import com.tangem.domain.tokens.model.TokenList +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.MultiCurrencyContent +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.SingleCurrencyContent +import com.tangem.feature.wallet.presentation.wallet.state.WalletsListConfig +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toPersistentList + +internal class TokenListToWalletStateConverter( + private val currentState: WalletStateHolder, + private val isWalletContentHidden: Boolean, + private val fiatCurrencyCode: String, + private val fiatCurrencySymbol: String, +) : Converter { + + override fun convert(value: TokenList): WalletStateHolder { + return when (currentState) { + is MultiCurrencyContent -> currentState.updateWithTokenList(value) + is SingleCurrencyContent -> currentState.updateWithTokenList(value) + } + } + + private fun MultiCurrencyContent.updateWithTokenList(tokenList: TokenList): MultiCurrencyContent { + val converter = TokenListToContentItemsConverter(isWalletContentHidden, fiatCurrencyCode, fiatCurrencySymbol) + + return this.copy( + walletsListConfig = updateSelectedWallet(tokenList.totalFiatBalance), + contentItems = converter.convert(tokenList), + ) + } + + private fun SingleCurrencyContent.updateWithTokenList(tokenList: TokenList): SingleCurrencyContent { + return this.copy( + walletsListConfig = updateSelectedWallet(tokenList.totalFiatBalance), + ) + } + + private fun WalletStateHolder.updateSelectedWallet(fiatBalance: TokenList.FiatBalance): WalletsListConfig { + val selectedWalletIndex = walletsListConfig.selectedWalletIndex + val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex] + val converter = FiatBalanceToWalletCardConverter( + selectedWalletCard, + isWalletContentHidden, + fiatCurrencyCode, + fiatCurrencySymbol, + ) + + return walletsListConfig.copy( + wallets = walletsListConfig.wallets + .toPersistentList() + .set(selectedWalletIndex, converter.convert(fiatBalance)), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenStatusToTokenItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenStatusToTokenItemConverter.kt new file mode 100644 index 0000000000..b654b47713 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenStatusToTokenItemConverter.kt @@ -0,0 +1,105 @@ +package com.tangem.feature.wallet.presentation.wallet.utils + +import androidx.annotation.DrawableRes +import com.tangem.core.ui.components.marketprice.PriceChangeConfig +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import com.tangem.utils.converter.Converter +import java.math.BigDecimal + +internal class TokenStatusToTokenItemConverter( + private val isWalletContentHidden: Boolean, + private val fiatCurrencyCode: String, + private val fiatCurrencySymbol: String, +) : Converter { + + private val TokenStatus.networkIconResId: Int? + @DrawableRes get() { + // TODO: [REDACTED_JIRA] + return if (isCoin) null else R.drawable.img_eth_22 + } + + private val TokenStatus.tokenIconResId: Int + @DrawableRes get() { + // TODO: [REDACTED_JIRA] + return R.drawable.img_eth_22 + } + + override fun convert(value: TokenStatus): TokenItemState { + return when (value.value) { + is TokenStatus.Loading -> TokenItemState.Loading + is TokenStatus.Loaded, + is TokenStatus.Custom, + -> value.mapToTokenItemState() + // TODO: Add other token item states, currently not designed + is TokenStatus.MissedDerivation, + is TokenStatus.NoAccount, + is TokenStatus.Unreachable, + -> value.mapToUnreachableTokenItemState() + } + } + + private fun TokenStatus.mapToTokenItemState(): TokenItemState.Content { + return TokenItemState.Content( + id = this.id.value, + name = this.name, + tokenIconUrl = this.iconUrl, + tokenIconResId = this.tokenIconResId, + networkIconResId = this.networkIconResId, + amount = getFormattedAmount(), + hasPending = value.hasTransactionsInProgress, + tokenOptions = if (isWalletContentHidden) { + TokenItemState.TokenOptionsState.Hidden(getPriceChangeConfig()) + } else { + TokenItemState.TokenOptionsState.Visible( + fiatAmount = getFormattedFiatAmount(), + priceChange = getPriceChangeConfig(), + ) + }, + ) + } + + private fun TokenStatus.getFormattedAmount(): String { + val amount = value.amount ?: return UNKNOWN_AMOUNT_SIGN + + return BigDecimalFormatter.formatCryptoAmount(amount, symbol, decimals) + } + + private fun TokenStatus.getFormattedFiatAmount(): String { + val fiatAmount = value.fiatAmount ?: return UNKNOWN_AMOUNT_SIGN + + return BigDecimalFormatter.formatFiatAmount(fiatAmount, fiatCurrencyCode, fiatCurrencySymbol) + } + + private fun TokenStatus.mapToUnreachableTokenItemState() = TokenItemState.Unreachable( + id = this.id.value, + name = this.name, + tokenIconUrl = this.iconUrl, + tokenIconResId = this.tokenIconResId, + networkIconResId = this.networkIconResId, + ) + + private fun TokenStatus.getPriceChangeConfig(): PriceChangeConfig { + val priceChange = value.priceChange + ?: return PriceChangeConfig(UNKNOWN_AMOUNT_SIGN, PriceChangeConfig.Type.DOWN) + + return PriceChangeConfig( + valueInPercent = BigDecimalFormatter.formatPercent(priceChange, useAbsoluteValue = true), + type = priceChange.getPriceChangeType(), + ) + } + + private fun BigDecimal?.getPriceChangeType(): PriceChangeConfig.Type { + return when { + this == null -> PriceChangeConfig.Type.DOWN + this < BigDecimal.ZERO -> PriceChangeConfig.Type.DOWN + else -> PriceChangeConfig.Type.UP + } + } + + private companion object { + const val UNKNOWN_AMOUNT_SIGN = "—" + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 2a2f30dcbb..9ee5a8a430 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -6,16 +6,26 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import arrow.core.Either import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig +import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens +import com.tangem.feature.wallet.presentation.wallet.utils.TokenErrorToWalletStateConverter +import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.properties.Delegates @@ -27,7 +37,6 @@ import kotlin.properties.Delegates */ @HiltViewModel internal class WalletViewModel @Inject constructor( - @Suppress("unused") // TODO: [REDACTED_JIRA] private val getTokenListUseCase: GetTokenListUseCase, private val scanCardProcessor: ScanCardProcessor, private val dispatchers: CoroutineDispatcherProvider, @@ -40,14 +49,29 @@ internal class WalletViewModel @Inject constructor( var uiState by mutableStateOf(getInitialState()) private set + private var getTokenListJob: Job? = null + set(value) { + field?.cancel() + field = value + } + // TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData - private fun getInitialState(): WalletStateHolder = WalletPreviewData.multicurrencyWalletScreenState.copy( - onBackClick = ::onBackClick, - topBarConfig = createTopBarConfig(), - walletsListConfig = WalletPreviewData.multicurrencyWalletScreenState.walletsListConfig.copy( - onWalletChange = ::selectWallet, - ), - ) + private fun getInitialState(): WalletStateHolder { + val state = WalletPreviewData.multicurrencyWalletScreenState.copy( + onBackClick = ::onBackClick, + topBarConfig = createTopBarConfig(), + walletsListConfig = WalletPreviewData.multicurrencyWalletScreenState.walletsListConfig.copy( + onWalletChange = ::selectWallet, + ), + contentItems = getLoadingMultiCurrencyTokens(), + ) + + val selectedWalletIndex = state.walletsListConfig.selectedWalletIndex + val selectedWalletId = WalletPreviewData.wallets.keys.elementAt(selectedWalletIndex) + launchGetTokenListJob(selectedWalletId) + + return state + } private fun onBackClick() { router.popBackStack() @@ -74,14 +98,45 @@ internal class WalletViewModel @Inject constructor( Log.i("WalletViewModel", "selectWallet: $index") - uiState = if (index % 2 == 0) { - WalletPreviewData.multicurrencyWalletScreenState.copy( + uiState = when (val state = uiState) { + is WalletStateHolder.MultiCurrencyContent -> state.copy( walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), ) - } else { - WalletPreviewData.singleWalletScreenState.copy( + is WalletStateHolder.SingleCurrencyContent -> state.copy( walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), ) } + + val selectedWalletId = WalletPreviewData.wallets.keys.elementAt(index) + launchGetTokenListJob(selectedWalletId) + } + + private fun launchGetTokenListJob(userWalletId: UserWalletId) { + getTokenListJob = getTokenListUseCase(userWalletId) + .distinctUntilChanged() + .mapLatest(::updateStateWithTokenListOrError) + .onEach { uiState = it } + .flowOn(Dispatchers.Default) + .launchIn(viewModelScope) + } + + private fun updateStateWithTokenListOrError(tokenList: Either): WalletStateHolder { + val updateStateWithError = { error: TokensError -> + val converter = TokenErrorToWalletStateConverter(uiState) + + converter.convert(error) + } + val updateState = { list: TokenList -> + val converter = TokenListToWalletStateConverter( + uiState, + isWalletContentHidden = false, // TODO: [REDACTED_JIRA] + fiatCurrencyCode = "USD", // TODO: [REDACTED_JIRA] + fiatCurrencySymbol = "$", // TODO: [REDACTED_JIRA] + ) + + converter.convert(list) + } + + return tokenList.fold(ifLeft = updateStateWithError, ifRight = updateState) } } \ No newline at end of file From de899b5678cbeee965f3f9d0ebf11ff1b9d13262 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 14 Jul 2023 11:37:10 +0300 Subject: [PATCH 41/58] Updated on 2026-08-14 --- .../com/tangem/tap/common/chat/ChatManager.kt | 8 +++- .../tap/common/chat/opener/ChatOpener.kt | 3 +- .../implementation/SprinklrChatOpener.kt | 5 +- .../implementation/ZendeskChatOpener.kt | 32 +++++++++---- .../tap/common/feedback/FeedbackManager.kt | 48 +++++++++++++++---- 5 files changed, 73 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt index 182aa7350b..56ded693ad 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt @@ -12,6 +12,7 @@ import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener import com.tangem.tap.common.chat.opener.implementation.ZendeskChatOpener import com.tangem.tap.common.redux.AppState import org.rekotlin.Store +import java.io.File class ChatManager( private val preferencesStorage: PreferencesDataSource, @@ -20,7 +21,7 @@ class ChatManager( ) { private val openers = mutableMapOf() - fun open(config: ChatConfig, feedbackDataBuilder: (Context) -> String) { + fun open(config: ChatConfig, createLogsFile: (Context) -> File?, createFeedbackFile: (Context) -> File?) { val opener = openers.getOrPut(config) { when (config) { is SprinklrConfig -> SprinklrChatOpener(getSprinklrUserId(), config, store, foregroundActivityObserver) @@ -28,7 +29,10 @@ class ChatManager( } } - opener.open(feedbackDataBuilder) + opener.open( + createFeedbackFile = createFeedbackFile, + createLogsFile = createLogsFile, + ) } private fun getZendeskUserId(): String { diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt index e3757286e7..aff21cad05 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/ChatOpener.kt @@ -1,7 +1,8 @@ package com.tangem.tap.common.chat.opener import android.content.Context +import java.io.File internal interface ChatOpener { - fun open(feedbackDataBuilder: (Context) -> String) + fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt index c9b8fdb4f9..f8936d6abd 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/SprinklrChatOpener.kt @@ -2,14 +2,15 @@ package com.tangem.tap.common.chat.opener.implementation import android.content.Context import android.content.Intent +import com.tangem.datasource.config.models.SprinklrConfig import com.tangem.tap.ForegroundActivityObserver import com.tangem.tap.common.chat.opener.ChatOpener -import com.tangem.datasource.config.models.SprinklrConfig import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.sprinklr.redux.SprinklrAction import com.tangem.tap.features.sprinklr.ui.SprinklrActivity import com.tangem.tap.withForegroundActivity import org.rekotlin.Store +import java.io.File internal class SprinklrChatOpener( private val userId: String, @@ -17,7 +18,7 @@ internal class SprinklrChatOpener( private val store: Store, private val foregroundActivityObserver: ForegroundActivityObserver, ) : ChatOpener { - override fun open(feedbackDataBuilder: (Context) -> String) { + override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) { store.dispatch(SprinklrAction.Init(userId, config)) foregroundActivityObserver.withForegroundActivity { activity -> val intent = Intent(activity, SprinklrActivity::class.java) diff --git a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt index 0e5d38b0da..1c6198806c 100644 --- a/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt +++ b/app/src/main/java/com/tangem/tap/common/chat/opener/implementation/ZendeskChatOpener.kt @@ -2,34 +2,34 @@ package com.tangem.tap.common.chat.opener.implementation import android.content.Context import com.tangem.core.analytics.Analytics +import com.tangem.datasource.config.models.ZendeskConfig import com.tangem.domain.common.LogConfig import com.tangem.tap.ForegroundActivityObserver import com.tangem.tap.common.chat.opener.ChatOpener -import com.tangem.datasource.config.models.ZendeskConfig import com.tangem.tap.withForegroundActivity import com.tangem.wallet.R import com.zendesk.logger.Logger -import zendesk.chat.Chat -import zendesk.chat.ChatConfiguration -import zendesk.chat.ChatEngine -import zendesk.chat.ChatProvidersConfiguration -import zendesk.chat.VisitorInfo +import timber.log.Timber +import zendesk.chat.* import zendesk.configurations.Configuration import zendesk.messaging.MessagingActivity +import java.io.File internal class ZendeskChatOpener( private val userId: String, private val config: ZendeskConfig, private val foregroundActivityObserver: ForegroundActivityObserver, ) : ChatOpener { + private var isInitialized = false - override fun open(feedbackDataBuilder: (Context) -> String) { + override fun open(createFeedbackFile: (Context) -> File?, createLogsFile: (Context) -> File?) { foregroundActivityObserver.withForegroundActivity { activity -> initZendeskIfNeeded(activity.applicationContext) setChatVisitorInfo() - setChatVisitorNote(feedbackDataBuilder(activity)) showMessagingActivity(activity) + sendFeedbackFile(createFeedbackFile(activity)) + sendLogsFile(createLogsFile(activity)) } } @@ -48,8 +48,20 @@ internal class ZendeskChatOpener( ChatProvidersConfiguration.builder().withVisitorInfo(visitorInfo).build() } - private fun setChatVisitorNote(note: String) { - Chat.INSTANCE.providers()?.profileProvider()?.setVisitorNote(note) + private fun sendFeedbackFile(feedbackFile: File?) { + if (isInitialized && feedbackFile != null) { + Chat.INSTANCE.providers()?.chatProvider()?.sendFile(feedbackFile) { _, bytesUploaded, _ -> + Timber.d("Log file sent", "bytesUploaded: $bytesUploaded") + } + } + } + + private fun sendLogsFile(logsFile: File?) { + if (isInitialized && logsFile != null) { + Chat.INSTANCE.providers()?.chatProvider()?.sendFile(logsFile) { _, bytesUploaded, _ -> + Timber.d("Log file sent", "bytesUploaded: $bytesUploaded") + } + } } private fun showMessagingActivity(context: Context) { diff --git a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt index 6fddf99607..c1e9ae43b1 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/FeedbackManager.kt @@ -1,9 +1,9 @@ package com.tangem.tap.common.feedback import android.content.Context +import com.tangem.datasource.config.models.ChatConfig import com.tangem.domain.common.TapWorkarounds import com.tangem.tap.common.chat.ChatManager -import com.tangem.datasource.config.models.ChatConfig import com.tangem.tap.common.extensions.sendEmail import com.tangem.tap.common.log.TangemLogCollector import com.tangem.tap.foregroundActivityObserver @@ -22,32 +22,61 @@ class FeedbackManager( private val chatManager: ChatManager, ) { + private var sessionFeedbackFile: File? = null + private var sessionLogsFile: File? = null + fun sendEmail(feedbackData: FeedbackData, onFail: ((Exception) -> Unit)? = null) { feedbackData.prepare(infoHolder) foregroundActivityObserver.withForegroundActivity { activity -> - val fileLog = createLogFile(activity) activity.sendEmail( email = getSupportEmail(), subject = activity.getString(feedbackData.subjectResId), message = feedbackData.joinTogether(activity, infoHolder), - file = fileLog, + file = getLogFile(activity), onFail = onFail, ) } } fun openChat(config: ChatConfig, feedbackData: FeedbackData) { - chatManager.open(config) { context -> - feedbackData.run { + chatManager.open( + config = config, + createLogsFile = ::getLogFile, + createFeedbackFile = { context -> getFeedbackFile(context, feedbackData) }, + ) + } + + private fun getFeedbackFile(context: Context, feedbackData: FeedbackData): File? { + return try { + if (sessionFeedbackFile != null) { + return sessionFeedbackFile + } + val file = File(context.filesDir, FEEDBACK_FILE) + file.delete() + file.createNewFile() + + val feedback = feedbackData.run { prepare(infoHolder) joinTogether(context, infoHolder) } + val fileWriter = FileWriter(file) + fileWriter.write(feedback) + fileWriter.close() + + sessionFeedbackFile = file + sessionFeedbackFile + } catch (ex: Exception) { + Timber.e(ex, "Can't create the logs file") + null } } - private fun createLogFile(context: Context): File? { + private fun getLogFile(context: Context): File? { return try { - val file = File(context.filesDir, "logs.txt") + if (sessionLogsFile != null) { + return sessionLogsFile + } + val file = File(context.filesDir, LOGS_FILE) file.delete() file.createNewFile() @@ -57,7 +86,8 @@ class FeedbackManager( fileWriter.write(stringWriter.toString()) fileWriter.close() logCollector.clearLogs() - file + sessionLogsFile = file + sessionLogsFile } catch (ex: Exception) { Timber.e(ex, "Can't create the logs file") null @@ -75,5 +105,7 @@ class FeedbackManager( companion object { const val DEFAULT_SUPPORT_EMAIL = "support@tangem.com" const val S2C_SUPPORT_EMAIL = "cardsupport@start2coin.com" + const val FEEDBACK_FILE = "feedback.txt" + const val LOGS_FILE = "logs.txt" } } \ No newline at end of file From 1a73ce8390a17e0f4e2223f2f6f5b0f188b78d08 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 14 Jul 2023 13:05:56 +0300 Subject: [PATCH 42/58] Updated on 2026-08-14 --- .../tangem/tap/common/extensions/Specific.kt | 32 ++++++++++++++++--- .../tap/features/wallet/ui/BalanceWidget.kt | 8 ++--- .../wallet/ui/WalletDetailsFragment.kt | 6 ++-- .../wallet/ui/adapters/WalletAdapter.kt | 4 +-- .../wallet/ui/utils/WalletDataOperations.kt | 6 ++-- 5 files changed, 39 insertions(+), 17 deletions(-) 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 bea9728880..5f259977e0 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 @@ -24,13 +24,17 @@ fun BigDecimal.toFormattedString( df.decimalFormatSymbols = symbols df.maximumFractionDigits = decimals df.minimumFractionDigits = 0 - df.isGroupingUsed = false + df.isGroupingUsed = true df.roundingMode = roundingMode return df.format(this) } +/** + * To formatted crypto currency string + * Specific method because there is no crypto currency codes in Locale + */ @Suppress("MagicNumber") -fun BigDecimal.toFormattedCurrencyString( +fun BigDecimal.toFormattedCryptoCurrencyString( decimals: Int, currency: String, roundingMode: RoundingMode = RoundingMode.DOWN, @@ -41,16 +45,34 @@ fun BigDecimal.toFormattedCurrencyString( } else { decimals } + try { + val locale = Locale.getDefault() + val formatter = NumberFormat.getCurrencyInstance(locale) + // first create currency instance for "USD" with Locale default to replace currency to crypto later + Currency.getInstance("USD")?.let { currencyTmp -> + formatter.currency = currencyTmp + formatter.maximumFractionDigits = decimalsForRounding + formatter.minimumFractionDigits = 0 + formatter.isGroupingUsed = true + formatter.roundingMode = roundingMode + // cause formatter created for USD, replace Currency with Crypto symbol on right by Locale place + return formatter.format(this).replace(currencyTmp.getSymbol(locale), "$currency ") + } + } catch (e: IllegalArgumentException) { + Timber.e(e, "can't parse currency") + } + // if something went wrong - use old way to format val formattedAmount = this.toFormattedString( decimals = decimalsForRounding, roundingMode = roundingMode, + locale = Locale.getDefault(), ) - return "$formattedAmount $currency" + return "$formattedAmount $currency " } fun BigDecimal.toFiatRateString(fiatCurrencyName: String, fiatCode: String): String { try { - val formatter = NumberFormat.getCurrencyInstance() + val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault()) Currency.getInstance(fiatCode)?.let { currency -> formatter.currency = currency formatter.maximumFractionDigits = 2 @@ -91,7 +113,7 @@ fun BigDecimal.toFormattedFiatValue( formatWithSpaces: Boolean = false, ): String { try { - val formatter = NumberFormat.getCurrencyInstance() + val formatter = NumberFormat.getCurrencyInstance(Locale.getDefault()) Currency.getInstance(fiatCode)?.let { currency -> formatter.currency = currency formatter.maximumFractionDigits = 2 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 23aa7d9d6e..3cab6df274 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 @@ -4,7 +4,7 @@ import androidx.annotation.IdRes import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.domain.model.WalletDataModel -import com.tangem.tap.features.wallet.ui.utils.getFormattedAmount +import com.tangem.tap.features.wallet.ui.utils.getFormattedCryptoAmount import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount import com.tangem.tap.store import com.tangem.wallet.R @@ -102,8 +102,8 @@ class BalanceWidget( groupBaseCurrency.show() tvCurrency.text = tokenWalletData?.currency?.currencySymbol tvBaseCurrency.text = data.currency.currencyName - tvAmount.text = if (showAmount) tokenWalletData?.getFormattedAmount() else "" - tvBaseAmount.text = if (showAmount) data.getFormattedAmount() else "" + tvAmount.text = if (showAmount) tokenWalletData?.getFormattedCryptoAmount() else "" + tvBaseAmount.text = if (showAmount) data.getFormattedCryptoAmount() else "" if (showAmount) { tvFiatAmount.show() tvFiatAmount.text = tokenWalletData?.getFormattedFiatAmount(store.state.globalState.appCurrency) @@ -113,7 +113,7 @@ class BalanceWidget( private fun showBalanceWithoutToken(data: WalletDataModel, showAmount: Boolean) = with(binding.lBalance) { groupBaseCurrency.hide() tvCurrency.text = data.currency.currencyName - tvAmount.text = if (showAmount) data.getFormattedAmount() else "" + tvAmount.text = if (showAmount) data.getFormattedCryptoAmount() else "" if (showAmount) { tvFiatAmount.show() tvFiatAmount.text = data.getFormattedFiatAmount(store.state.globalState.appCurrency) 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 c6cad8e1bc..1c0c5027f2 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,7 +59,7 @@ import com.tangem.tap.features.wallet.ui.images.load import com.tangem.tap.features.wallet.ui.test.TestWallet import com.tangem.tap.features.wallet.ui.utils.assembleWarnings import com.tangem.tap.features.wallet.ui.utils.getAvailableActions -import com.tangem.tap.features.wallet.ui.utils.getFormattedAmount +import com.tangem.tap.features.wallet.ui.utils.getFormattedCryptoAmount import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount import com.tangem.tap.features.wallet.ui.utils.isAvailableToBuy import com.tangem.tap.features.wallet.ui.utils.isAvailableToSell @@ -425,7 +425,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), SafeSt lBalance.root.show() lBalance.groupBalance.show() lBalance.tvError.hide() - lBalance.tvAmount.text = walletData.getFormattedAmount() + lBalance.tvAmount.text = walletData.getFormattedCryptoAmount() lBalance.tvFiatAmount.text = walletData.getFormattedFiatAmount(store.state.globalState.appCurrency) lBalance.tvStatus.setLoadingStatus(R.string.wallet_balance_loading) } @@ -437,7 +437,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), SafeSt lBalance.root.show() lBalance.groupBalance.show() lBalance.tvError.hide() - lBalance.tvAmount.text = walletData.getFormattedAmount() + lBalance.tvAmount.text = walletData.getFormattedCryptoAmount() lBalance.tvFiatAmount.text = walletData.getFormattedFiatAmount(store.state.globalState.appCurrency) when (status) { is WalletDataModel.VerifiedOnline, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt index 4aec238877..e0a15c7e4b 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WalletAdapter.kt @@ -15,7 +15,7 @@ import com.tangem.tap.common.extensions.show import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.features.wallet.ui.images.load -import com.tangem.tap.features.wallet.ui.utils.getFormattedAmount +import com.tangem.tap.features.wallet.ui.utils.getFormattedCryptoAmount import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatAmount import com.tangem.tap.features.wallet.ui.utils.getFormattedFiatRate import com.tangem.tap.store @@ -85,7 +85,7 @@ class WalletAdapter : ListAdapter Date: Wed, 12 Jul 2023 09:21:30 +0300 Subject: [PATCH 43/58] Updated on 2026-08-14 --- .../wallet2/viewmodel/SeedPhraseViewModel.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 2e6e7587eb..8dcb8ff0d6 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -169,13 +169,17 @@ class SeedPhraseViewModel @Inject constructor( if (fieldState.isError != hasError) { updateUi { uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError) } } - - val allFieldsWithoutError = SeedPhraseField.values() + val isCreateWalletButtonEnabled = SeedPhraseField.values() .map { field -> field.getState(uiState) } - .all { fieldState -> !fieldState.isError } + .all { fieldState -> fieldState.textFieldValue.text.isNotEmpty() && !fieldState.isError } - if (uiState.checkSeedPhraseState.buttonCreateWallet.enabled != allFieldsWithoutError) { - updateUi { uiBuilder.checkSeedPhrase.updateCreateWalletButton(uiState, allFieldsWithoutError) } + if (uiState.checkSeedPhraseState.buttonCreateWallet.enabled != isCreateWalletButtonEnabled) { + updateUi { + uiBuilder.checkSeedPhrase.updateCreateWalletButton( + uiState = uiState, + enabled = isCreateWalletButtonEnabled, + ) + } } } } From 929d547a70f7c0b0e74acd8b663ed2d481e26fb5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Jul 2023 08:15:28 +0300 Subject: [PATCH 44/58] Updated on 2026-08-14 --- .../tangem/core/ui/components/buttons/common/TangemButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt index b0a5b59116..c995a22c0e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt @@ -30,7 +30,7 @@ internal fun TangemButton( elevation = elevation, shape = size.toShape(), colors = colors, - contentPadding = size.toContentPadding(icon = icon), + contentPadding = if (showProgress) ButtonDefaults.ContentPadding else size.toContentPadding(icon = icon), ) { ButtonContent( text = text, From 982d3731e0b2d15edacbf5830c03842f6f975b46 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Jul 2023 14:56:53 +0500 Subject: [PATCH 45/58] Updated on 2026-08-14 --- .../buttons/HorizontalActionChips.kt | 88 +++++++++++++++++++ .../presentation/common/WalletPreviewData.kt | 4 +- .../wallet/state/WalletStateHolder.kt | 3 +- .../presentation/wallet/ui/WalletScreen.kt | 8 +- .../singlecurrency/WalletManageButtons.kt | 67 -------------- 5 files changed, 97 insertions(+), 73 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/WalletManageButtons.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt new file mode 100644 index 0000000000..1646791082 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt @@ -0,0 +1,88 @@ +package com.tangem.core.ui.components.buttons + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R +import com.tangem.core.ui.components.buttons.actions.ActionButton +import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig +import com.tangem.core.ui.res.TangemTheme +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +@Composable +fun HorizontalActionChips( + buttons: ImmutableList, + modifier: Modifier = Modifier, + contentPadding: PaddingValues = PaddingValues(0.dp), +) { + LazyRow( + modifier = modifier + .background(color = TangemTheme.colors.background.secondary) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8), + verticalAlignment = Alignment.CenterVertically, + contentPadding = contentPadding, + ) { + items( + items = buttons, + key = { it.text }, + itemContent = { ActionButton(config = it) }, + ) + } +} + +@Preview +@Composable +private fun Preview_HorizontalActionChips_Light( + @PreviewParameter(ActionButtonConfigProvider::class) buttons: ImmutableList, +) { + TangemTheme(isDark = false) { + HorizontalActionChips(buttons = buttons) + } +} + +@Preview +@Composable +private fun Preview_HorizontalActionChips_Dark( + @PreviewParameter(ActionButtonConfigProvider::class) buttons: ImmutableList, +) { + TangemTheme(isDark = true) { + HorizontalActionChips(buttons = buttons) + } +} + +private class ActionButtonConfigProvider : CollectionPreviewParameterProvider( + collection = persistentListOf( + ActionButtonConfig( + text = "Buy", + iconResId = R.drawable.ic_plus_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Send", + iconResId = R.drawable.ic_arrow_up_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Receive", + iconResId = R.drawable.ic_arrow_down_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Exchange", + iconResId = R.drawable.ic_exchange_vertical_24, + onClick = {}, + ), + ), +) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index dab4813f30..f535f5d114 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -200,7 +200,7 @@ internal object WalletPreviewData { ), ) - val manageButtons = persistentListOf( + private val manageButtons = persistentListOf( WalletManageButton.Buy(onClick = {}), WalletManageButton.Send(onClick = {}), WalletManageButton.Receive(onClick = {}), @@ -294,7 +294,7 @@ internal object WalletPreviewData { ), ), notifications = persistentListOf(WalletNotification.LikeTangemApp(onClick = {})), - buttons = manageButtons, + buttons = manageButtons.map(WalletManageButton::config).toPersistentList(), marketPriceBlockState = MarketPriceBlockState.Content( currencyName = "BTC", price = "98900.12$", diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index b9b216a198..12fb96d14e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state +import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import kotlinx.collections.immutable.ImmutableList @@ -58,7 +59,7 @@ internal sealed class WalletStateHolder( override val walletsListConfig: WalletsListConfig, override val contentItems: ImmutableList, override val notifications: ImmutableList, - val buttons: ImmutableList, + val buttons: ImmutableList, val marketPriceBlockState: MarketPriceBlockState, ) : WalletStateHolder(onBackClick, topBarConfig, walletsListConfig, contentItems, notifications) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index b89486d90e..e281fb492e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.buttons.actions.RoundedActionButton +import com.tangem.core.ui.components.buttons.HorizontalActionChips import com.tangem.core.ui.components.marketprice.MarketPriceBlock import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.transactions.Transaction @@ -33,7 +34,6 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletTopBar import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockGroupTitle import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockTitle -import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.WalletManageButtons import com.tangem.feature.wallet.presentation.wallet.ui.decorations.walletContentItemDecoration import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator @@ -73,9 +73,11 @@ internal fun WalletScreen(state: WalletStateHolder) { if (state is WalletStateHolder.SingleCurrencyContent) { item { - WalletManageButtons( + HorizontalActionChips( buttons = state.buttons, - modifier = changeableItemModifier.padding(top = TangemTheme.dimens.spacing14), + modifier = changeableItemModifier + .padding(top = TangemTheme.dimens.spacing14), + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/WalletManageButtons.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/WalletManageButtons.kt deleted file mode 100644 index 19b1a57045..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/singlecurrency/WalletManageButtons.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.components.buttons.actions.ActionButton -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButton -import kotlinx.collections.immutable.ImmutableList - -/** - * Wallet manage buttons - * - * @param buttons manage buttons - * @param modifier modifier - * -[REDACTED_AUTHOR] - */ -@Composable -internal fun WalletManageButtons(buttons: ImmutableList, modifier: Modifier = Modifier) { - LazyRow( - modifier = modifier - .background(color = TangemTheme.colors.background.secondary) - .fillMaxWidth(), - contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), - horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8), - verticalAlignment = Alignment.CenterVertically, - ) { - items( - items = buttons, - key = { it.config.text }, - itemContent = { ActionButton(config = it.config) }, - ) - } -} - -@Preview -@Composable -private fun Preview_WalletManageButtons_Light( - @PreviewParameter(WalletManageButtonProvider::class) buttons: ImmutableList, -) { - TangemTheme(isDark = false) { - WalletManageButtons(buttons = buttons) - } -} - -@Preview -@Composable -private fun Preview_WalletManageButtons_Dark( - @PreviewParameter(WalletManageButtonProvider::class) buttons: ImmutableList, -) { - TangemTheme(isDark = true) { - WalletManageButtons(buttons = buttons) - } -} - -private class WalletManageButtonProvider : CollectionPreviewParameterProvider>( - collection = listOf(WalletPreviewData.manageButtons), -) \ No newline at end of file From 4a0978c18637192c976bc4c9e2f7ebe3d078ea8b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Jul 2023 16:51:53 +0300 Subject: [PATCH 46/58] Updated on 2026-08-14 --- .../features/tokens/impl/presentation/TokensListFragment.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt index 8af509f84f..417a308de0 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/TokensListFragment.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.fragment.app.Fragment import androidx.hilt.navigation.compose.hiltViewModel import androidx.transition.TransitionInflater +import com.tangem.core.ui.components.SystemBarsEffect import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.tokens.impl.presentation.ui.TokensListScreen import com.tangem.tap.features.tokens.impl.presentation.viewmodels.TokensListViewModel @@ -40,6 +41,10 @@ internal class TokensListFragment : Fragment() { } TangemTheme { + val statusBarColor = TangemTheme.colors.background.secondary + SystemBarsEffect { + setSystemBarsColor(color = statusBarColor) + } TokensListScreen( modifier = Modifier.systemBarsPadding(), stateHolder = viewModel.uiState, From c82c2b3cfba4809daaa5f31a82f303c11a140596 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Jul 2023 21:11:12 +0800 Subject: [PATCH 47/58] Updated on 2026-08-14 --- .../common/analytics/topup/TopUpController.kt | 2 +- .../di/domain/wallets/WalletsDomainModule.kt | 7 ++++ .../common/feedback/AdditionalFeedbackInfo.kt | 9 ++--- .../tangem/tap/di/domain/CardDomainModule.kt | 28 +++++++++++++++ .../com/tangem/tap/domain/extensions/Card.kt | 23 ++---------- .../tap/domain/tokens/UserTokensRepository.kt | 2 +- .../details/redux/DetailsMiddleware.kt | 4 +-- .../tap/features/home/redux/HomeMiddleware.kt | 2 +- .../features/onboarding/OnboardingHelper.kt | 2 +- .../twins/redux/TwinCardsMiddleware.kt | 2 +- .../products/twins/ui/TwinsCardsFragment.kt | 10 ++---- .../redux/OnboardingWalletMiddleware.kt | 2 +- .../saveWallet/redux/SaveWalletMiddleware.kt | 2 +- .../tap/features/wallet/redux/WalletState.kt | 17 +-------- .../redux/middlewares/WalletMiddleware.kt | 2 +- .../wallet/redux/reducers/WalletReducer.kt | 8 +++-- .../redux/WalletSelectorMiddleware.kt | 4 +-- .../welcome/redux/WelcomeMiddleware.kt | 2 +- .../tangem/tap/proxy/UserWalletManagerImpl.kt | 2 +- data/card/build.gradle.kts | 1 + .../card/DefaultCardSdkConfigRepository.kt | 9 +++-- .../com/tangem/data/card/di/CardDataModule.kt | 11 ++++-- .../card/GetAccessCodeSavingStatusUseCase.kt | 15 ++++++++ .../domain/card/GetBiometricsStatusUseCase.kt | 15 ++++++++ .../card/SetAccessCodeRequestPolicyUseCase.kt | 17 +++++++++ .../repository/CardSdkConfigRepository.kt | 3 ++ domain/legacy/build.gradle.kts | 1 + .../com/tangem/domain/userwallets/Artwork.kt | 14 ++++++++ .../userwallets}/GetCardImageUseCase.kt | 3 +- .../domain/userwallets}/UserWalletBuilder.kt | 6 ++-- .../userwallets}/UserWalletIdBuilder.kt | 2 +- domain/wallets/build.gradle.kts | 1 + .../domain/wallets/models/SaveWalletError.kt | 10 ++++++ .../wallets/usecase/SaveWalletUseCase.kt | 28 +++++++++++++++ features/wallet/impl/build.gradle.kts | 1 + .../wallet/viewmodels/WalletViewModel.kt | 36 +++++++++++++++++-- 36 files changed, 223 insertions(+), 80 deletions(-) create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/GetAccessCodeSavingStatusUseCase.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/GetBiometricsStatusUseCase.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/SetAccessCodeRequestPolicyUseCase.kt create mode 100644 domain/legacy/src/main/java/com/tangem/domain/userwallets/Artwork.kt rename {app/src/main/java/com/tangem/tap/domain/userWalletList => domain/legacy/src/main/java/com/tangem/domain/userwallets}/GetCardImageUseCase.kt (95%) rename {app/src/main/java/com/tangem/tap/domain/model/builders => domain/legacy/src/main/java/com/tangem/domain/userwallets}/UserWalletBuilder.kt (88%) rename {app/src/main/java/com/tangem/tap/domain/model/builders => domain/legacy/src/main/java/com/tangem/domain/userwallets}/UserWalletIdBuilder.kt (98%) create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/models/SaveWalletError.kt create mode 100644 domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt b/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt index bdd315bc3d..ba23fa52dd 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt @@ -9,6 +9,7 @@ import com.tangem.domain.common.BlockchainNetwork import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.common.analytics.converters.TopUpEventConverter @@ -17,7 +18,6 @@ import com.tangem.tap.common.extensions.copy 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.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager import com.tangem.tap.domain.walletStores.WalletStoresManager import com.tangem.tap.features.wallet.models.Currency diff --git a/app/src/main/java/com/tangem/tap/common/di/domain/wallets/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/common/di/domain/wallets/WalletsDomainModule.kt index f945462b83..bcf08adf87 100644 --- a/app/src/main/java/com/tangem/tap/common/di/domain/wallets/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/common/di/domain/wallets/WalletsDomainModule.kt @@ -2,6 +2,7 @@ package com.tangem.tap.common.di.domain.wallets import com.tangem.domain.wallets.legacy.WalletsStateHolder import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.domain.wallets.usecase.SaveWalletUseCase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -17,4 +18,10 @@ object WalletsDomainModule { fun providesGetWalletsUseCase(walletsStateHolder: WalletsStateHolder): GetWalletsUseCase { return GetWalletsUseCase(walletsStateHolder = walletsStateHolder) } + + @Provides + @ViewModelScoped + fun providesSaveWalletUseCase(walletsStateHolder: WalletsStateHolder): SaveWalletUseCase { + return SaveWalletUseCase(walletsStateHolder = walletsStateHolder) + } } \ No newline at end of file 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 7d138f1610..947691b26f 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 @@ -1,17 +1,12 @@ package com.tangem.tap.common.feedback import android.os.Build -import com.tangem.blockchain.common.Amount -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.Wallet -import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.* import com.tangem.blockchain.common.address.Address import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.tap.common.extensions.stripZeroPlainString -import com.tangem.tap.domain.model.builders.UserWalletIdBuilder class AdditionalFeedbackInfo { class EmailWalletInfo( diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 27d0e3e8df..4af9f75d50 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -1,6 +1,10 @@ package com.tangem.tap.di.domain +import com.tangem.domain.card.GetAccessCodeSavingStatusUseCase +import com.tangem.domain.card.GetBiometricsStatusUseCase import com.tangem.domain.card.ScanCardProcessor +import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase +import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor import dagger.Module import dagger.Provides @@ -15,4 +19,28 @@ internal object CardDomainModule { @Provides @Singleton fun provideScanCardUseCase(): ScanCardProcessor = DefaultScanCardProcessor() + + @Provides + @Singleton + fun provideGetBiometricsStatusUseCase( + cardSdkConfigRepository: CardSdkConfigRepository, + ): GetBiometricsStatusUseCase { + return GetBiometricsStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository) + } + + @Provides + @Singleton + fun provideSetAccessCodeRequestPolicyUseCase( + cardSdkConfigRepository: CardSdkConfigRepository, + ): SetAccessCodeRequestPolicyUseCase { + return SetAccessCodeRequestPolicyUseCase(cardSdkConfigRepository = cardSdkConfigRepository) + } + + @Provides + @Singleton + fun provideGetAccessCodeSavingStatusUseCase( + cardSdkConfigRepository: CardSdkConfigRepository, + ): GetAccessCodeSavingStatusUseCase { + return GetAccessCodeSavingStatusUseCase(cardSdkConfigRepository = cardSdkConfigRepository) + } } \ No newline at end of file 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 f963c6f982..363565bf3e 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 @@ -1,24 +1,17 @@ package com.tangem.tap.domain.extensions -import com.tangem.common.card.FirmwareVersion import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.getTwinCardNumber +import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.userwallets.Artwork import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.OnlineCardVerifier -import com.tangem.tap.features.wallet.redux.Artwork val CardDTO.remainingSignatures: Int? get() = this.wallets.firstOrNull()?.remainingSignatures -val CardDTO.isWalletDataSupported: Boolean - get() = this.firmwareVersion.major >= 4 - -val CardDTO.isFirmwareMultiwalletAllowed: Boolean - get() = firmwareVersion >= FirmwareVersion.MultiWalletAvailable && settings.maxWalletsCount > 1 - val CardDTO.isHdWalletAllowedByApp: Boolean get() = settings.isHDWalletAllowed @@ -58,16 +51,4 @@ suspend fun CardDTO.getOrLoadCardArtworkUrl(cardInfo: Result ifAnyError() } -} - -fun CardDTO.getArtworkUrl(artworkId: String?): String? { - return when { - artworkId != null -> { - OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) - } - - cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL - cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL - else -> null - } } \ 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 c4c01684bf..6842f8a94c 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 @@ -10,7 +10,7 @@ import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.files.AndroidFileReader import com.tangem.domain.common.BlockchainNetwork import com.tangem.domain.models.scan.CardDTO -import com.tangem.tap.domain.model.builders.UserWalletIdBuilder +import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.tap.domain.tokens.converters.CurrencyConverter import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.wallet.models.Currency 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 e665b7cee3..478d0cb1bd 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 @@ -12,6 +12,8 @@ import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.TapWorkarounds.isTangemTwins import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -23,8 +25,6 @@ import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.model.builders.UserWalletBuilder -import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.di.provideRuntimeImplementation import com.tangem.tap.domain.userWalletList.isLockedSync 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 547440e96f..15a3f5c172 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 @@ -9,6 +9,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.tap.common.analytics.events.Basic import com.tangem.tap.common.analytics.events.IntroductionProcess import com.tangem.tap.common.analytics.events.Shop @@ -19,7 +20,6 @@ import com.tangem.tap.common.extensions.eraseContext import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.model.builders.UserWalletBuilder 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.BUY_WALLET_URL 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 477ab679c6..baf5ed2fcf 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 @@ -10,12 +10,12 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.tap.* import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.extensions.removeContext import com.tangem.tap.common.extensions.setContext -import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import kotlinx.coroutines.delay import kotlinx.coroutines.launch 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 af0338bb49..b768c53091 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 @@ -9,6 +9,7 @@ import com.tangem.core.navigation.NavigationAction import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.twinsIsTwinned import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Onboarding @@ -19,7 +20,6 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.makePrimaryWalletManager -import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.domain.userWalletList.isLockedSync import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE 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 77bc7f7310..2fa0a10909 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 @@ -17,15 +17,12 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.ShareElement import com.tangem.core.ui.fragments.setStatusBarColor import com.tangem.datasource.asset.AssetReader -import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.common.TwinCardNumber +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.Artwork import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.analytics.events.Onboarding -import com.tangem.tap.common.extensions.beginDelayedTransition -import com.tangem.tap.common.extensions.getDrawableCompat -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget import com.tangem.tap.common.transitions.InternalNoteLayoutTransition import com.tangem.tap.domain.twins.TwinsCardWidget @@ -35,7 +32,6 @@ import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletM import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep -import com.tangem.tap.features.wallet.redux.Artwork import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding 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 fe5800266a..2057707f3d 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 @@ -13,6 +13,7 @@ import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.Artwork import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.operations.backup.BackupService @@ -29,7 +30,6 @@ import com.tangem.tap.features.home.redux.HomeAction import com.tangem.tap.features.onboarding.OnboardingDialog import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.wallet.models.toCurrencies -import com.tangem.tap.features.wallet.redux.Artwork import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.launch import org.rekotlin.Action 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 370e759dff..8261f5bef3 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 @@ -9,6 +9,7 @@ import com.tangem.common.flatMap import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction +import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam @@ -18,7 +19,6 @@ import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.userWalletList.di.provideBiometricImplementation import com.tangem.tap.domain.userWalletList.isLockable import com.tangem.tap.features.wallet.redux.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 2135a4ab7d..0048c80636 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 @@ -1,11 +1,11 @@ package com.tangem.tap.features.wallet.redux -import android.graphics.Bitmap import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.WalletManager import com.tangem.domain.common.BlockchainNetwork import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.userwallets.Artwork import com.tangem.tap.common.entities.Button import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.common.toggleWidget.WidgetState @@ -118,19 +118,4 @@ enum class ErrorType { sealed class WalletMainButton(enabled: Boolean) : Button(enabled) { class SendButton(enabled: Boolean) : WalletMainButton(enabled) -} - -data class Artwork( - val artworkId: String, - val artwork: Bitmap? = null, -) { - companion object { - const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" - const val SERGIO_CARD_URL = "https://app.tangem.com/cards/card_tg059.png" - const val MARTA_CARD_URL = "https://app.tangem.com/cards/card_tg083.png" - const val SERGIO_CARD_ID = "BC01" - const val MARTA_CARD_ID = "BC02" - const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" - const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" - } } \ 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 2bdd72972f..a96e7e6197 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 @@ -12,6 +12,7 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.datasource.connection.NetworkConnectionManager +import com.tangem.domain.userwallets.GetCardImageUseCase import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic @@ -23,7 +24,6 @@ import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TapError import com.tangem.tap.domain.model.WalletDataModel import com.tangem.tap.domain.model.WalletStoreModel -import com.tangem.tap.domain.userWalletList.GetCardImageUseCase import com.tangem.tap.domain.userWalletList.lockIfLockable import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.features.home.redux.HomeAction 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 ac44f59413..e91e0a1194 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 @@ -4,12 +4,16 @@ import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.address.AddressType import com.tangem.domain.common.TapWorkarounds.isTestCard import com.tangem.domain.models.scan.CardDTO +import com.tangem.domain.userwallets.Artwork import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapError 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.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.WalletState import com.tangem.tap.proxy.AppStateHolder import com.tangem.tap.userWalletsListManager import org.rekotlin.Action @@ -115,7 +119,7 @@ private fun internalReduce(action: Action, state: AppState, appStateHolder: AppS if (selectedUserWallet == action.userWalletId) { newState = newState.copy( - cardImage = Artwork(artworkId = action.url, artwork = newState.cardImage?.artwork), + cardImage = Artwork(artworkId = action.url), ) } } 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 a331a52719..acac8cd6a8 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 @@ -6,6 +6,8 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.tap.* @@ -18,8 +20,6 @@ import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.model.TotalFiatBalance import com.tangem.tap.domain.model.WalletStoreModel -import com.tangem.tap.domain.model.builders.UserWalletBuilder -import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.domain.userWalletList.unlockIfLockable import com.tangem.tap.proxy.redux.DaggerGraphState import kotlinx.coroutines.Dispatchers 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 c10e1bcad2..f18499bf50 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 @@ -8,6 +8,7 @@ import com.tangem.common.flatMap import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic @@ -15,7 +16,6 @@ import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState -import com.tangem.tap.domain.model.builders.UserWalletBuilder import com.tangem.tap.domain.userWalletList.unlockIfLockable import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler import com.tangem.tap.features.signin.redux.SignInAction 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 1938886ede..432dc559a1 100644 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt @@ -10,6 +10,7 @@ import com.tangem.domain.common.BlockchainNetwork 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.userwallets.UserWalletIdBuilder import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.models.Currency import com.tangem.lib.crypto.models.Currency.NativeToken @@ -17,7 +18,6 @@ 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.common.extensions.dispatchOnMain -import com.tangem.tap.domain.model.builders.UserWalletIdBuilder import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.userWalletsListManager import com.tangem.tap.walletCurrenciesManager diff --git a/data/card/build.gradle.kts b/data/card/build.gradle.kts index 69bbb44e78..26b3010f2e 100644 --- a/data/card/build.gradle.kts +++ b/data/card/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(deps.tangem.card.android) implementation(deps.tangem.card.core) + implementation(projects.data.source.preferences) implementation(projects.domain.card) implementation(projects.domain.models) } \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt index e263616c2f..321a48b0bf 100644 --- a/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt +++ b/data/card/src/main/java/com/tangem/data/card/DefaultCardSdkConfigRepository.kt @@ -5,18 +5,21 @@ import com.tangem.common.UserCodeType import com.tangem.common.core.CardIdDisplayFormat import com.tangem.common.core.UserCodeRequestPolicy import com.tangem.data.card.sdk.CardSdkProvider +import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.models.scan.ProductType /** - * Implementation of repository for managing with CardSDK config + * Implementation of repository for managing of CardSDK config * - * @property cardSdkProvider CardSDK instance provider + * @property cardSdkProvider CardSDK instance provider + * @property preferencesDataSource application shared preferences * [REDACTED_AUTHOR] */ internal class DefaultCardSdkConfigRepository( private val cardSdkProvider: CardSdkProvider, + private val preferencesDataSource: PreferencesDataSource, ) : CardSdkConfigRepository { @Deprecated("Use CardSdkConfigRepository's methods instead of this property") @@ -50,4 +53,6 @@ internal class DefaultCardSdkConfigRepository( -> CardIdDisplayFormat.Full } } + + override fun isAccessCodeSavingEnabled(): Boolean = preferencesDataSource.shouldSaveAccessCodes } \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt b/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt index 4ed7adf268..11f63f4a31 100644 --- a/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt +++ b/data/card/src/main/java/com/tangem/data/card/di/CardDataModule.kt @@ -2,6 +2,7 @@ package com.tangem.data.card.di import com.tangem.data.card.DefaultCardSdkConfigRepository import com.tangem.data.card.sdk.CardSdkProvider +import com.tangem.data.source.preferences.PreferencesDataSource import com.tangem.domain.card.repository.CardSdkConfigRepository import dagger.Module import dagger.Provides @@ -15,7 +16,13 @@ internal object CardDataModule { @Provides @Singleton - fun provideCardSdkConfigRepository(cardSdkProvider: CardSdkProvider): CardSdkConfigRepository { - return DefaultCardSdkConfigRepository(cardSdkProvider = cardSdkProvider) + fun provideCardSdkConfigRepository( + cardSdkProvider: CardSdkProvider, + preferencesDataSource: PreferencesDataSource, + ): CardSdkConfigRepository { + return DefaultCardSdkConfigRepository( + cardSdkProvider = cardSdkProvider, + preferencesDataSource = preferencesDataSource, + ) } } \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/GetAccessCodeSavingStatusUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/GetAccessCodeSavingStatusUseCase.kt new file mode 100644 index 0000000000..538ac0f240 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/GetAccessCodeSavingStatusUseCase.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.card + +import com.tangem.domain.card.repository.CardSdkConfigRepository + +/** + * Use case for getting access code saving status + * + * @property cardSdkConfigRepository repository for managing of CardSDK config + * +[REDACTED_AUTHOR] + */ +class GetAccessCodeSavingStatusUseCase(private val cardSdkConfigRepository: CardSdkConfigRepository) { + + operator fun invoke(): Boolean = cardSdkConfigRepository.isAccessCodeSavingEnabled() +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/GetBiometricsStatusUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/GetBiometricsStatusUseCase.kt new file mode 100644 index 0000000000..a917d6f691 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/GetBiometricsStatusUseCase.kt @@ -0,0 +1,15 @@ +package com.tangem.domain.card + +import com.tangem.domain.card.repository.CardSdkConfigRepository + +/** + * Check if config has biometrics request policy + * + * @property cardSdkConfigRepository repository for managing of CardSDK config + * +[REDACTED_AUTHOR] + */ +class GetBiometricsStatusUseCase(private val cardSdkConfigRepository: CardSdkConfigRepository) { + + operator fun invoke(): Boolean = cardSdkConfigRepository.isBiometricsRequestPolicy() +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/SetAccessCodeRequestPolicyUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/SetAccessCodeRequestPolicyUseCase.kt new file mode 100644 index 0000000000..e10f624726 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/SetAccessCodeRequestPolicyUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.card + +import com.tangem.domain.card.repository.CardSdkConfigRepository + +/** + * Set access code request policy by 'isBiometricsRequestPolicy' + * + * @property cardSdkConfigRepository repository for managing of CardSDK config + * +[REDACTED_AUTHOR] + */ +class SetAccessCodeRequestPolicyUseCase(private val cardSdkConfigRepository: CardSdkConfigRepository) { + + operator fun invoke(isBiometricsRequestPolicy: Boolean) { + cardSdkConfigRepository.setAccessCodeRequestPolicy(isBiometricsRequestPolicy) + } +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt index 11fa7e26e1..1190a2952d 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/CardSdkConfigRepository.kt @@ -25,4 +25,7 @@ interface CardSdkConfigRepository { /** Update the card ID display format according to the [productType] of the scanned card */ fun updateCardIdDisplayFormat(productType: ProductType) + + /** Check if access code saving is enabled */ + fun isAccessCodeSavingEnabled(): Boolean } \ No newline at end of file diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index e9449ad7de..525d2c8504 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { implementation(project(":common")) implementation(project(":libs:auth")) implementation(project(":domain:models")) + implementation(projects.domain.wallets.models) /** Tangem libraries */ implementation(deps.tangem.blockchain) { diff --git a/domain/legacy/src/main/java/com/tangem/domain/userwallets/Artwork.kt b/domain/legacy/src/main/java/com/tangem/domain/userwallets/Artwork.kt new file mode 100644 index 0000000000..d788aed532 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/userwallets/Artwork.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.userwallets + +data class Artwork(val artworkId: String) { + + companion object { + const val DEFAULT_IMG_URL = "https://app.tangem.com/cards/card_default.png" + const val SERGIO_CARD_URL = "https://app.tangem.com/cards/card_tg059.png" + const val MARTA_CARD_URL = "https://app.tangem.com/cards/card_tg083.png" + const val SERGIO_CARD_ID = "BC01" + const val MARTA_CARD_ID = "BC02" + const val TWIN_CARD_1 = "https://app.tangem.com/cards/card_tg085.png" + const val TWIN_CARD_2 = "https://app.tangem.com/cards/card_tg086.png" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt b/domain/legacy/src/main/java/com/tangem/domain/userwallets/GetCardImageUseCase.kt similarity index 95% rename from app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt rename to domain/legacy/src/main/java/com/tangem/domain/userwallets/GetCardImageUseCase.kt index 9d218c840f..afa5eaaa37 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/GetCardImageUseCase.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/userwallets/GetCardImageUseCase.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain.userWalletList +package com.tangem.domain.userwallets import com.tangem.common.extensions.toHexString import com.tangem.common.services.Result @@ -6,7 +6,6 @@ import com.tangem.domain.common.TwinCardNumber import com.tangem.domain.common.TwinsHelper import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.operations.attestation.TangemApi -import com.tangem.tap.features.wallet.redux.Artwork /** * Use case for getting card image url diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt similarity index 88% rename from app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt rename to domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt index 7fa72a04fa..565930ae9d 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletBuilder.kt @@ -1,12 +1,10 @@ -package com.tangem.tap.domain.model.builders +package com.tangem.domain.userwallets -import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ProductType import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.models.UserWallet -import com.tangem.tap.domain.userWalletList.GetCardImageUseCase class UserWalletBuilder( private val scanResponse: ScanResponse, @@ -24,7 +22,7 @@ class UserWalletBuilder( ProductType.Start2Coin -> "Start2Coin" ProductType.Wallet -> when { card.isBackupNotAllowed -> "Tangem card" - card.isStart2Coin -> "Start2Coin" + cardTypesResolver.isStart2Coin() -> "Start2Coin" else -> "Wallet" } } diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt b/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt similarity index 98% rename from app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt rename to domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt index 30b4ef4407..cad0957152 100644 --- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletIdBuilder.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/userwallets/UserWalletIdBuilder.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.domain.model.builders +package com.tangem.domain.userwallets import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.hexToBytes diff --git a/domain/wallets/build.gradle.kts b/domain/wallets/build.gradle.kts index a9a1449956..31807a9a48 100644 --- a/domain/wallets/build.gradle.kts +++ b/domain/wallets/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { // endregion // region Other libraries + implementation(deps.arrow.core) implementation(deps.kotlin.coroutines) // endregion } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SaveWalletError.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SaveWalletError.kt new file mode 100644 index 0000000000..97ff31ef11 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/models/SaveWalletError.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.wallets.models + +/** +[REDACTED_AUTHOR] + */ +sealed interface SaveWalletError { + + // TODO: Finalize in next PRs + object CommonError : SaveWalletError +} \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt new file mode 100644 index 0000000000..3afdb23663 --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt @@ -0,0 +1,28 @@ +package com.tangem.domain.wallets.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.common.doOnFailure +import com.tangem.common.doOnSuccess +import com.tangem.domain.wallets.legacy.WalletsStateHolder +import com.tangem.domain.wallets.models.SaveWalletError +import com.tangem.domain.wallets.models.UserWallet + +/** + * Use case for saving user wallet + * + * @property walletsStateHolder state holder for getting static initialized 'userWalletsListManager' + * +[REDACTED_AUTHOR] + */ +class SaveWalletUseCase(private val walletsStateHolder: WalletsStateHolder) { + + suspend operator fun invoke(userWallet: UserWallet, canOverride: Boolean = false): Either { + requireNotNull(walletsStateHolder.userWalletsListManager).save(userWallet, canOverride) + .doOnSuccess { return Unit.right() } + .doOnFailure { return SaveWalletError.CommonError.left() } + + return Unit.right() + } +} \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index c216409615..55e9edc0c3 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(deps.compose.reorderable) /** Other libraries */ + implementation(deps.arrow.core) implementation(deps.kotlin.immutable.collections) implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 9ee5a8a430..d47726ddeb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -9,8 +9,13 @@ import androidx.lifecycle.viewModelScope import arrow.core.Either import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.domain.card.GetAccessCodeSavingStatusUseCase +import com.tangem.domain.card.GetBiometricsStatusUseCase import com.tangem.domain.card.ScanCardProcessor +import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.usecase.SaveWalletUseCase import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWalletId @@ -35,8 +40,13 @@ import kotlin.properties.Delegates * [REDACTED_AUTHOR] */ +@Suppress("LongParameterList") @HiltViewModel internal class WalletViewModel @Inject constructor( + private val saveWalletUseCase: SaveWalletUseCase, + private val getBiometricsStatusUseCase: GetBiometricsStatusUseCase, + private val setAccessCodeRequestPolicyUseCase: SetAccessCodeRequestPolicyUseCase, + private val getAccessCodeSavingStatusUseCase: GetAccessCodeSavingStatusUseCase, private val getTokenListUseCase: GetTokenListUseCase, private val scanCardProcessor: ScanCardProcessor, private val dispatchers: CoroutineDispatcherProvider, @@ -85,10 +95,32 @@ internal class WalletViewModel @Inject constructor( } private fun onScanCardClick() { + val prevRequestPolicyStatus = getBiometricsStatusUseCase() + + // Update access the code policy according access code saving status + setAccessCodeRequestPolicyUseCase(isBiometricsRequestPolicy = getAccessCodeSavingStatusUseCase()) + viewModelScope.launch(dispatchers.io) { scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true) - .doOnSuccess { /* [REDACTED_TODO_COMMENT] */ } - .doOnFailure { /* [REDACTED_TODO_COMMENT] */ } + .doOnSuccess { + // If card's public key is null then user wallet will be null + val userWallet = UserWalletBuilder(scanResponse = it).build() + + if (userWallet != null) { + saveWalletUseCase(userWallet) + .onLeft { + // Rollback policy if card saving was failed + setAccessCodeRequestPolicyUseCase(prevRequestPolicyStatus) + } + } else { + // Rollback policy if card saving was failed + setAccessCodeRequestPolicyUseCase(prevRequestPolicyStatus) + } + } + .doOnFailure { + // Rollback policy if card scanning was failed + setAccessCodeRequestPolicyUseCase(prevRequestPolicyStatus) + } } } From 33f237c1a5037f4c8aabe3fb6a94a8f59d12e937 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Jul 2023 10:52:46 +0300 Subject: [PATCH 48/58] Updated on 2026-08-14 --- .../presentation/common/WalletPreviewData.kt | 8 + .../wallet/state/WalletPullToRefreshConfig.kt | 9 + .../wallet/state/WalletStateHolder.kt | 60 ++++--- .../presentation/wallet/ui/WalletScreen.kt | 159 ++++++++++-------- 4 files changed, 146 insertions(+), 90 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletPullToRefreshConfig.kt diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index f535f5d114..bc2443bc0e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -261,6 +261,10 @@ internal object WalletPreviewData { ), ), ), + pullToRefreshConfig = WalletPullToRefreshConfig( + isRefreshing = false, + onRefresh = {}, + ), notifications = persistentListOf( WalletNotification.UnreachableNetworks, WalletNotification.LikeTangemApp(onClick = {}), @@ -293,6 +297,10 @@ internal object WalletPreviewData { ), ), ), + pullToRefreshConfig = WalletPullToRefreshConfig( + isRefreshing = false, + onRefresh = {}, + ), notifications = persistentListOf(WalletNotification.LikeTangemApp(onClick = {})), buttons = manageButtons.map(WalletManageButton::config).toPersistentList(), marketPriceBlockState = MarketPriceBlockState.Content( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletPullToRefreshConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletPullToRefreshConfig.kt new file mode 100644 index 0000000000..8aafa384f0 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletPullToRefreshConfig.kt @@ -0,0 +1,9 @@ +package com.tangem.feature.wallet.presentation.wallet.state + +/** + * Wallet screen top bar config + * + * @property isRefreshing state is indicator visible + * @property onRefresh lambda be invoked when pulled to refresh + */ +data class WalletPullToRefreshConfig(val isRefreshing: Boolean, val onRefresh: () -> Unit) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index 12fb96d14e..c4b9c37ee8 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -7,11 +7,12 @@ import kotlinx.collections.immutable.ImmutableList /** * Wallet screen state holder * - * @property onBackClick lambda be invoked when back button is clicked - * @property topBarConfig top bar config - * @property walletsListConfig wallets list config - * @property contentItems content items - * @property notifications notifications + * @property onBackClick lambda be invoked when back button is clicked + * @property topBarConfig top bar config + * @property walletsListConfig wallets list config + * @property pullToRefreshConfig pull to refresh config + * @property contentItems content items + * @property notifications notifications * [REDACTED_AUTHOR] */ @@ -19,6 +20,7 @@ internal sealed class WalletStateHolder( open val onBackClick: () -> Unit, open val topBarConfig: WalletTopBarConfig, open val walletsListConfig: WalletsListConfig, + open val pullToRefreshConfig: WalletPullToRefreshConfig, open val contentItems: ImmutableList, open val notifications: ImmutableList, ) { @@ -26,40 +28,58 @@ internal sealed class WalletStateHolder( /** * Multi currency wallet content state * - * @property onBackClick lambda be invoked when back button is clicked - * @property topBarConfig top bar config - * @property walletsListConfig wallets list config - * @property contentItems content items - * @property notifications notifications - * @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked + * @property onBackClick lambda be invoked when back button is clicked + * @property topBarConfig top bar config + * @property walletsListConfig wallets list config + * @property pullToRefreshConfig pull to refresh config + * @property contentItems content items + * @property notifications notifications + * @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked */ data class MultiCurrencyContent( override val onBackClick: () -> Unit, override val topBarConfig: WalletTopBarConfig, override val walletsListConfig: WalletsListConfig, + override val pullToRefreshConfig: WalletPullToRefreshConfig, override val contentItems: ImmutableList, override val notifications: ImmutableList, val onOrganizeTokensClick: () -> Unit, - ) : WalletStateHolder(onBackClick, topBarConfig, walletsListConfig, contentItems, notifications) + ) : WalletStateHolder( + onBackClick = onBackClick, + topBarConfig = topBarConfig, + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + contentItems = contentItems, + notifications = notifications, + ) /** * Single currency wallet content state * - * @property onBackClick lambda be invoked when back button is clicked - * @property topBarConfig top bar config - * @property walletsListConfig wallets list config - * @property contentItems content items - * @property notifications notifications - * @property buttons manage buttons - * @property marketPriceBlockState market price block state + * @property onBackClick lambda be invoked when back button is clicked + * @property topBarConfig top bar config + * @property walletsListConfig wallets list config + * @property pullToRefreshConfig pull to refresh config + * @property contentItems content items + * @property notifications notifications + * @property buttons manage buttons + * @property marketPriceBlockState market price block state */ data class SingleCurrencyContent( override val onBackClick: () -> Unit, override val topBarConfig: WalletTopBarConfig, override val walletsListConfig: WalletsListConfig, + override val pullToRefreshConfig: WalletPullToRefreshConfig, override val contentItems: ImmutableList, override val notifications: ImmutableList, val buttons: ImmutableList, val marketPriceBlockState: MarketPriceBlockState, - ) : WalletStateHolder(onBackClick, topBarConfig, walletsListConfig, contentItems, notifications) + ) : WalletStateHolder( + onBackClick = onBackClick, + topBarConfig = topBarConfig, + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + contentItems = contentItems, + notifications = notifications, + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index e281fb492e..42f5485b26 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -8,6 +8,10 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -44,6 +48,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimat * [REDACTED_AUTHOR] */ +@OptIn(ExperimentalMaterialApi::class) @Suppress("LongMethod") @Composable internal fun WalletScreen(state: WalletStateHolder) { @@ -56,88 +61,102 @@ internal fun WalletScreen(state: WalletStateHolder) { val walletsListState = rememberLazyListState() val changeableItemModifier = Modifier.changeWalletAnimator(walletsListState) + val pullRefreshState = rememberPullRefreshState( + refreshing = state.pullToRefreshConfig.isRefreshing, + onRefresh = state.pullToRefreshConfig.onRefresh, + ) - LazyColumn( + Box( modifier = Modifier .padding(paddingValues = scaffoldPaddings) - .fillMaxSize(), - contentPadding = PaddingValues(vertical = TangemTheme.dimens.spacing8), - horizontalAlignment = Alignment.CenterHorizontally, + .pullRefresh(pullRefreshState), ) { - item { - WalletsList( - config = state.walletsListConfig, - lazyListState = walletsListState, - ) - } - - if (state is WalletStateHolder.SingleCurrencyContent) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(vertical = TangemTheme.dimens.spacing8), + horizontalAlignment = Alignment.CenterHorizontally, + ) { item { - HorizontalActionChips( - buttons = state.buttons, - modifier = changeableItemModifier - .padding(top = TangemTheme.dimens.spacing14), - contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + WalletsList( + config = state.walletsListConfig, + lazyListState = walletsListState, ) } - } - items( - items = state.notifications, - itemContent = { item -> - Notification( - state = item.state, - modifier = changeableItemModifier - .padding(top = TangemTheme.dimens.spacing14) - .padding(horizontal = TangemTheme.dimens.spacing16), - ) - }, - ) - - if (state is WalletStateHolder.SingleCurrencyContent) { - item { - MarketPriceBlock( - state = state.marketPriceBlockState, - modifier = changeableItemModifier - .padding(top = TangemTheme.dimens.spacing14) - .padding(horizontal = TangemTheme.dimens.spacing16), - ) - } - } - - itemsIndexed( - items = state.contentItems, - key = { index, item -> - when (item) { - is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> item.networkName - is WalletContentItemState.MultiCurrencyItem.Token -> index - is WalletContentItemState.SingleCurrencyItem.Title -> index - is WalletContentItemState.SingleCurrencyItem.GroupTitle -> item.title - is WalletContentItemState.SingleCurrencyItem.Transaction -> index - is WalletContentItemState.Loading -> index + if (state is WalletStateHolder.SingleCurrencyContent) { + item { + HorizontalActionChips( + buttons = state.buttons, + modifier = changeableItemModifier + .padding(top = TangemTheme.dimens.spacing14), + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16), + ) } - }, - itemContent = { index, item -> - ContentItem( - item = item, - modifier = changeableItemModifier.walletContentItemDecoration( - currentIndex = index, - lastIndex = state.contentItems.lastIndex, - ), - ) - }, - ) + } - if (state is WalletStateHolder.MultiCurrencyContent) { - item { - OrganizeTokensButton( - onClick = state.onOrganizeTokensClick, - modifier = changeableItemModifier - .padding(top = TangemTheme.dimens.spacing14) - .padding(horizontal = TangemTheme.dimens.spacing16), - ) + items( + items = state.notifications, + itemContent = { item -> + Notification( + state = item.state, + modifier = changeableItemModifier + .padding(top = TangemTheme.dimens.spacing14) + .padding(horizontal = TangemTheme.dimens.spacing16), + ) + }, + ) + + if (state is WalletStateHolder.SingleCurrencyContent) { + item { + MarketPriceBlock( + state = state.marketPriceBlockState, + modifier = changeableItemModifier + .padding(top = TangemTheme.dimens.spacing14) + .padding(horizontal = TangemTheme.dimens.spacing16), + ) + } + } + + itemsIndexed( + items = state.contentItems, + key = { index, item -> + when (item) { + is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> item.networkName + is WalletContentItemState.MultiCurrencyItem.Token -> index + is WalletContentItemState.SingleCurrencyItem.Title -> index + is WalletContentItemState.SingleCurrencyItem.GroupTitle -> item.title + is WalletContentItemState.SingleCurrencyItem.Transaction -> index + is WalletContentItemState.Loading -> index + } + }, + itemContent = { index, item -> + ContentItem( + item = item, + modifier = changeableItemModifier.walletContentItemDecoration( + currentIndex = index, + lastIndex = state.contentItems.lastIndex, + ), + ) + }, + ) + + if (state is WalletStateHolder.MultiCurrencyContent) { + item { + OrganizeTokensButton( + onClick = state.onOrganizeTokensClick, + modifier = changeableItemModifier + .padding(top = TangemTheme.dimens.spacing14) + .padding(horizontal = TangemTheme.dimens.spacing16), + ) + } } } + + PullRefreshIndicator( + refreshing = state.pullToRefreshConfig.isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + ) } } } From 50c3b57a9113b21dc4bf5a65385e5a948fbe807f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Jul 2023 18:02:01 +0300 Subject: [PATCH 49/58] Updated on 2026-08-14 --- .../com/tangem/domain/common/extensions/Blockchain.kt | 8 +++++++- gradle/dependencies.toml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt index f4084f38df..15fb408bde 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt @@ -29,6 +29,7 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? { "bitcoin-cash/test" -> Blockchain.BitcoinCashTestnet "cardano" -> Blockchain.CardanoShelley "dogecoin" -> Blockchain.Dogecoin + "ducatus" -> Blockchain.Ducatus "litecoin" -> Blockchain.Litecoin "rootstock" -> Blockchain.RSK "stellar" -> Blockchain.Stellar @@ -86,6 +87,7 @@ fun Blockchain.toNetworkId(): String { Blockchain.Cardano -> "cardano" Blockchain.CardanoShelley -> "cardano" Blockchain.Dogecoin -> "dogecoin" + Blockchain.Ducatus -> "ducatus" Blockchain.Ethereum -> "ethereum" Blockchain.EthereumTestnet -> "ethereum/test" Blockchain.EthereumClassic -> "ethereum-classic" @@ -150,6 +152,7 @@ fun Blockchain.toCoinId(): String { Blockchain.Fantom, Blockchain.FantomTestnet -> "fantom" Blockchain.Tron, Blockchain.TronTestnet -> "tron" Blockchain.Polkadot, Blockchain.PolkadotTestnet -> "polkadot" + Blockchain.Ducatus -> "ducatus" Blockchain.Litecoin -> "litecoin" Blockchain.RSK -> "rootstock" Blockchain.Tezos -> "tezos" @@ -179,4 +182,7 @@ fun Blockchain.isSupportedInApp(): Boolean { return !excludedBlockchains.contains(this) } -private val excludedBlockchains = listOf(Blockchain.Unknown) \ No newline at end of file +private val excludedBlockchains = listOf( + Blockchain.Unknown, + Blockchain.Ducatus, +) \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index d2f864a9ab..f5f9705ca1 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -80,7 +80,7 @@ okHttp-prettyLogging = "3.1.0" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-281" +tangemBlockchainSdk = "develop-283" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-273" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds From 3d2ef29a24e639717f335d40eab3ed4e842f0c1b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 18 Jul 2023 21:55:50 +0500 Subject: [PATCH 50/58] Updated on 2026-08-14 --- features/tokendetails/impl/build.gradle.kts | 2 + .../di/TokenDetailsRouterModule.kt | 5 +- .../presentation/TokenDetailsFragment.kt | 46 ++++++ .../router/DefaultTokenDetailsRouter.kt | 15 +- .../router/InnerTokenDetailsRouter.kt | 8 + .../tokendetails/TokenDetailsPreviewData.kt | 41 +++++ .../tokendetails/state/TokenDetailsState.kt | 6 + .../state/TokenDetailsTopAppBarConfig.kt | 6 + .../tokendetails/state/TokenInfoBlockState.kt | 23 +++ .../tokendetails/ui/TokenDetailsScreen.kt | 47 ++++++ .../ui/components/TokenDetailsTopAppBar.kt | 58 +++++++ .../ui/components/TokenInfoBlock.kt | 141 ++++++++++++++++++ .../viewmodels/TokenDetailsViewModel.kt | 30 ++++ 13 files changed, 423 insertions(+), 5 deletions(-) create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsTopAppBarConfig.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsTopAppBar.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index aa624c46ac..604cb6ce5e 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) implementation(deps.compose.accompanist.systemUiController) + implementation(deps.compose.coil) /** DI */ implementation(deps.hilt.android) @@ -32,6 +33,7 @@ dependencies { /** Core modules */ implementation(projects.core.featuretoggles) implementation(projects.core.ui) + implementation(projects.core.navigation) /** Feature Apis */ implementation(projects.features.tokendetails.api) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsRouterModule.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsRouterModule.kt index 47a8b90f2e..3a0dc79713 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsRouterModule.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsRouterModule.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.di +import com.tangem.core.navigation.NavigationStateHolder import com.tangem.feature.tokendetails.presentation.router.DefaultTokenDetailsRouter import com.tangem.features.tokendetails.navigation.TokenDetailsRouter import dagger.Module @@ -14,5 +15,7 @@ internal object TokenDetailsRouterModule { @Provides @ActivityScoped - fun provideTokenDetailsRouter(): TokenDetailsRouter = DefaultTokenDetailsRouter() + fun provideTokenDetailsRouter(navigationStateHolder: NavigationStateHolder): TokenDetailsRouter { + return DefaultTokenDetailsRouter(navigationStateHolder) + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt new file mode 100644 index 0000000000..d9e0a1bcf7 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/TokenDetailsFragment.kt @@ -0,0 +1,46 @@ +package com.tangem.feature.tokendetails.presentation + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment +import androidx.hilt.navigation.compose.hiltViewModel +import com.tangem.core.ui.components.SystemBarsEffect +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen +import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsViewModel +import com.tangem.features.tokendetails.navigation.TokenDetailsRouter +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +@AndroidEntryPoint +internal class TokenDetailsFragment : Fragment() { + + @Inject + lateinit var tokenDetailsRouter: TokenDetailsRouter + + private val internalTokenDetailsRouter: InnerTokenDetailsRouter + get() = requireNotNull(tokenDetailsRouter as? InnerTokenDetailsRouter) { + "internalTokenDetailsRouter should be instance of InnerTokenDetailsRouter" + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return ComposeView(inflater.context).apply { + setContent { + TangemTheme { + val systemBarsColor = TangemTheme.colors.background.secondary + SystemBarsEffect { + setSystemBarsColor(systemBarsColor) + } + + val viewModel = hiltViewModel() + viewModel.router = this@TokenDetailsFragment.internalTokenDetailsRouter + TokenDetailsScreen(state = viewModel.uiState) + } + } + } + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt index 6bfaf4dcaa..73819028da 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/DefaultTokenDetailsRouter.kt @@ -1,10 +1,17 @@ package com.tangem.feature.tokendetails.presentation.router import androidx.fragment.app.Fragment -import com.tangem.features.tokendetails.navigation.TokenDetailsRouter +import com.tangem.core.navigation.NavigationAction +import com.tangem.core.navigation.NavigationStateHolder +import com.tangem.feature.tokendetails.presentation.TokenDetailsFragment -internal class DefaultTokenDetailsRouter : TokenDetailsRouter { +internal class DefaultTokenDetailsRouter( + private val navigationStateHolder: NavigationStateHolder, +) : InnerTokenDetailsRouter { - // TODO: Doston fix it in next PRs - override fun getEntryFragment(): Fragment = Fragment() + override fun getEntryFragment(): Fragment = TokenDetailsFragment() + + override fun popBackStack() { + navigationStateHolder.navigate(NavigationAction.PopBackTo()) + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt new file mode 100644 index 0000000000..89448deb0d --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/router/InnerTokenDetailsRouter.kt @@ -0,0 +1,8 @@ +package com.tangem.feature.tokendetails.presentation.router + +import com.tangem.features.tokendetails.navigation.TokenDetailsRouter + +internal interface InnerTokenDetailsRouter : TokenDetailsRouter { + + fun popBackStack() +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt new file mode 100644 index 0000000000..cb065cf32f --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -0,0 +1,41 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails + +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarConfig +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState +import com.tangem.features.tokendetails.impl.R + +internal object TokenDetailsPreviewData { + + val tokenDetailsTopAppBarConfig = TokenDetailsTopAppBarConfig(onBackClick = {}, onMoreClick = {}) + + val tokenInfoBlockStateWithLongNameInMainCurrency = TokenInfoBlockState( + name = "Stellar (XLM) with long name test", + iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png", + currency = TokenInfoBlockState.Currency.Native, + ) + val tokenInfoBlockStateWithLongName = TokenInfoBlockState( + name = "Tether (USDT) with long name test", + iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png", + currency = TokenInfoBlockState.Currency.Token( + networkName = "ERC20", + networkIcon = R.drawable.img_eth_22, + blockchainName = "Ethereum", + ), + ) + + val tokenInfoBlockState = TokenInfoBlockState( + name = "Tether USDT", + iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png", + currency = TokenInfoBlockState.Currency.Token( + networkName = "ERC20", + networkIcon = R.drawable.img_eth_22, + blockchainName = "Ethereum", + ), + ) + + val tokenDetailsState = TokenDetailsState( + topAppBarConfig = tokenDetailsTopAppBarConfig, + tokenInfoBlockState = tokenInfoBlockState, + ) +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt new file mode 100644 index 0000000000..438b92545d --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -0,0 +1,6 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state + +data class TokenDetailsState( + val topAppBarConfig: TokenDetailsTopAppBarConfig, + val tokenInfoBlockState: TokenInfoBlockState, +) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsTopAppBarConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsTopAppBarConfig.kt new file mode 100644 index 0000000000..8ed33f724b --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsTopAppBarConfig.kt @@ -0,0 +1,6 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state + +data class TokenDetailsTopAppBarConfig( + val onBackClick: () -> Unit, + val onMoreClick: () -> Unit, +) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt new file mode 100644 index 0000000000..ac4c80561c --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenInfoBlockState.kt @@ -0,0 +1,23 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state + +import androidx.annotation.DrawableRes + +data class TokenInfoBlockState( + val name: String, + val iconUrl: String, + val currency: Currency, +) { + sealed class Currency { + object Native : Currency() + + /** + * @param networkName - token standard. Samples: ERC20, BEP20, BEP2, TRC20 and etc. + * @param blockchainName - token's blockchain name. Ethereum, Tron and etc. + */ + data class Token( + val networkName: String, + val blockchainName: String, + @DrawableRes val networkIcon: Int, + ) : Currency() + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt new file mode 100644 index 0000000000..380d2ad51f --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -0,0 +1,47 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock + +@Composable +internal fun TokenDetailsScreen(state: TokenDetailsState) { + Scaffold( + topBar = { TokenDetailsTopAppBar(config = state.topAppBarConfig) }, + containerColor = TangemTheme.colors.background.secondary, + ) { scaffoldPaddings -> + Column( + modifier = Modifier + .padding(paddingValues = scaffoldPaddings) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxSize(), + ) { + TokenInfoBlock(state = state.tokenInfoBlockState) + } + } +} + +@Preview +@Composable +private fun Preview_TokenDetailsScreen_LightTheme() { + TangemTheme(isDark = false) { + TokenDetailsScreen(state = TokenDetailsPreviewData.tokenDetailsState) + } +} + +@Preview +@Composable +private fun Preview_TokenDetailsScreen_DarkTheme() { + TangemTheme(isDark = true) { + TokenDetailsScreen(state = TokenDetailsPreviewData.tokenDetailsState) + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsTopAppBar.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsTopAppBar.kt new file mode 100644 index 0000000000..a7901787f0 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsTopAppBar.kt @@ -0,0 +1,58 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components + +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData +import com.tangem.features.tokendetails.impl.R +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarConfig + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun TokenDetailsTopAppBar(config: TokenDetailsTopAppBarConfig) { + TopAppBar( + navigationIcon = { + IconButton(onClick = config.onBackClick) { + Icon( + painter = painterResource(id = R.drawable.ic_back_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = "Back", + ) + } + }, + title = {}, + actions = { + IconButton(onClick = config.onMoreClick) { + Icon( + painter = painterResource(id = R.drawable.ic_more_vertical_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = "More", + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = TangemTheme.colors.background.secondary, + titleContentColor = TangemTheme.colors.icon.primary1, + actionIconContentColor = TangemTheme.colors.icon.primary1, + ), + scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), + ) +} + +@Preview +@Composable +private fun Preview_TokenDetailsTopAppBar_LightTheme() { + TangemTheme(isDark = false) { + TokenDetailsTopAppBar(config = TokenDetailsPreviewData.tokenDetailsTopAppBarConfig) + } +} + +@Preview +@Composable +private fun Preview_TokenDetailsTopAppBar_DarkTheme() { + TangemTheme(isDark = true) { + TokenDetailsTopAppBar(config = TokenDetailsPreviewData.tokenDetailsTopAppBarConfig) + } +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt new file mode 100644 index 0000000000..96ac8f6bf3 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenInfoBlock.kt @@ -0,0 +1,141 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import coil.compose.rememberAsyncImagePainter +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState +import com.tangem.features.tokendetails.impl.R + +@Composable +internal fun TokenInfoBlock(state: TokenInfoBlockState, modifier: Modifier = Modifier) { + Row(modifier = modifier.fillMaxWidth()) { + Column( + modifier = Modifier.weight(1F), + ) { + Text( + text = state.name, + style = TangemTheme.typography.h1, + color = TangemTheme.colors.text.primary1, + ) + NetworkInfoText(state.currency) + } + + val tokenIconPainter = when (LocalInspectionMode.current) { + // show drawable res in preview + true -> painterResource(id = R.drawable.img_stellar_22) + false -> rememberAsyncImagePainter(model = state.iconUrl) + } + + Image( + modifier = Modifier.size(TangemTheme.dimens.size48), + painter = tokenIconPainter, + contentDescription = null, + ) + } +} + +@Composable +private fun NetworkInfoText(currency: TokenInfoBlockState.Currency) { + when (currency) { + TokenInfoBlockState.Currency.Native -> { + Text( + text = stringResource(id = R.string.common_main_network), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption, + ) + } + is TokenInfoBlockState.Currency.Token -> { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + ) { + val state = extractNetwork(tokenCurrency = currency) + Text( + text = state.normalText, + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.tertiary, + ) + Icon( + modifier = Modifier.size(TangemTheme.dimens.size16), + painter = painterResource(id = currency.networkIcon), + tint = Color.Unspecified, + contentDescription = null, + ) + Text( + text = state.boldText, + style = TangemTheme.typography.caption.copy(fontWeight = FontWeight.Medium), + color = TangemTheme.colors.text.primary1, + ) + } + } + } +} + +private const val SEPARATOR = " %image% " + +@Composable +private fun extractNetwork(tokenCurrency: TokenInfoBlockState.Currency.Token): ExtractedTokenNetworkText { + val splitString = stringResource( + id = R.string.token_details_token_type_subtitle, + formatArgs = arrayOf( + tokenCurrency.networkName, + tokenCurrency.blockchainName, + ), + ).split(SEPARATOR) + + return remember(splitString) { + ExtractedTokenNetworkText( + normalText = splitString.firstOrNull().orEmpty(), + boldText = splitString.getOrNull(1).orEmpty(), + ) + } +} + +private data class ExtractedTokenNetworkText(val normalText: String, val boldText: String) + +@Preview +@Composable +private fun Preview_TokenInfoBlock_LightTheme( + @PreviewParameter(TokenInfoStateProvider::class) + state: TokenInfoBlockState, +) { + TangemTheme(isDark = false) { + TokenInfoBlock(state, Modifier.background(TangemTheme.colors.background.secondary)) + } +} + +@Preview +@Composable +private fun Preview_TokenInfoBlock_DarkTheme( + @PreviewParameter(TokenInfoStateProvider::class) + state: TokenInfoBlockState, +) { + TangemTheme(isDark = true) { + TokenInfoBlock(state, Modifier.background(TangemTheme.colors.background.secondary)) + } +} + +private class TokenInfoStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + TokenDetailsPreviewData.tokenInfoBlockState, + TokenDetailsPreviewData.tokenInfoBlockStateWithLongName, + TokenDetailsPreviewData.tokenInfoBlockStateWithLongNameInMainCurrency, + ), +) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt new file mode 100644 index 0000000000..50e489eedb --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -0,0 +1,30 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlin.properties.Delegates + +@HiltViewModel +internal class TokenDetailsViewModel @Inject constructor() : ViewModel() { + + var router: InnerTokenDetailsRouter by Delegates.notNull() + + var uiState by mutableStateOf(getInitialState()) + private set + + private fun getInitialState() = TokenDetailsPreviewData.tokenDetailsState.copy( + topAppBarConfig = TokenDetailsPreviewData.tokenDetailsTopAppBarConfig.copy( + onBackClick = ::onBackClick, + ), + ) + + private fun onBackClick() { + router.popBackStack() + } +} \ No newline at end of file From 4430add6f05ee7ec7e2a40b23253b5f1fc583bde Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Jul 2023 14:15:27 +0500 Subject: [PATCH 51/58] Updated on 2026-08-14 --- .../buttons/HorizontalActionChips.kt | 8 +- .../core/ui/utils/BigDecimalFormatter.kt | 2 + features/tokendetails/impl/build.gradle.kts | 2 + .../tokendetails/TokenDetailsPreviewData.kt | 38 +++++ .../state/TokenDetailsBalanceBlockState.kt | 23 +++ .../tokendetails/state/TokenDetailsState.kt | 1 + .../tokendetails/ui/TokenDetailsScreen.kt | 4 + .../ui/components/TokenDetailsBalanceBlock.kt | 136 ++++++++++++++++++ .../viewmodels/TokenDetailsViewModel.kt | 13 ++ 9 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt create mode 100644 features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt index 1646791082..de7706a43d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/HorizontalActionChips.kt @@ -1,6 +1,5 @@ package com.tangem.core.ui.components.buttons -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth @@ -12,7 +11,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.components.buttons.actions.ActionButton import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig @@ -24,12 +22,10 @@ import kotlinx.collections.immutable.persistentListOf fun HorizontalActionChips( buttons: ImmutableList, modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp), + contentPadding: PaddingValues = PaddingValues(TangemTheme.dimens.spacing0), ) { LazyRow( - modifier = modifier - .background(color = TangemTheme.colors.background.secondary) - .fillMaxWidth(), + modifier = modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8), verticalAlignment = Alignment.CenterVertically, contentPadding = contentPadding, diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt index d10357cd54..2a78274eb5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt @@ -8,6 +8,8 @@ import java.util.Locale object BigDecimalFormatter { + const val EMPTY_BALANCE_SIGN = "—" + private const val TEMP_CURRENCY_CODE = "USD" fun formatCryptoAmount( diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 604cb6ce5e..d3af011dcd 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -26,6 +26,8 @@ dependencies { implementation(deps.compose.accompanist.systemUiController) implementation(deps.compose.coil) + implementation(deps.kotlin.immutable.collections) + /** DI */ implementation(deps.hilt.android) kapt(deps.hilt.kapt) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index cb065cf32f..c94cca8569 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -1,9 +1,13 @@ package com.tangem.feature.tokendetails.presentation.tokendetails +import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState import com.tangem.features.tokendetails.impl.R +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList internal object TokenDetailsPreviewData { @@ -34,8 +38,42 @@ internal object TokenDetailsPreviewData { ), ) + private val actionButtons = persistentListOf( + ActionButtonConfig( + text = "Buy", + iconResId = R.drawable.ic_plus_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Send", + iconResId = R.drawable.ic_arrow_up_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Receive", + iconResId = R.drawable.ic_arrow_down_24, + onClick = {}, + ), + ActionButtonConfig( + text = "Exchange", + iconResId = R.drawable.ic_exchange_vertical_24, + onClick = {}, + ), + ) + + private val disabledActionButtons = actionButtons.map { it.copy(enabled = false) }.toPersistentList() + + val balanceLoading = TokenDetailsBalanceBlockState.Loading(actionButtons = disabledActionButtons) + val balanceContent = TokenDetailsBalanceBlockState.Content( + actionButtons = actionButtons, + fiatBalance = "123,00$", + cryptoBalance = "866,96 USDT", + ) + val balanceError = TokenDetailsBalanceBlockState.Error(actionButtons = disabledActionButtons) + val tokenDetailsState = TokenDetailsState( topAppBarConfig = tokenDetailsTopAppBarConfig, tokenInfoBlockState = tokenInfoBlockState, + tokenBalanceBlockState = balanceLoading, ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt new file mode 100644 index 0000000000..a67edd1a61 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsBalanceBlockState.kt @@ -0,0 +1,23 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state + +import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig +import kotlinx.collections.immutable.ImmutableList + +sealed class TokenDetailsBalanceBlockState { + + abstract val actionButtons: ImmutableList + + data class Loading( + override val actionButtons: ImmutableList, + ) : TokenDetailsBalanceBlockState() + + data class Content( + override val actionButtons: ImmutableList, + val fiatBalance: String, + val cryptoBalance: String, + ) : TokenDetailsBalanceBlockState() + + data class Error( + override val actionButtons: ImmutableList, + ) : TokenDetailsBalanceBlockState() +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt index 438b92545d..988dd22bfa 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -3,4 +3,5 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state data class TokenDetailsState( val topAppBarConfig: TokenDetailsTopAppBarConfig, val tokenInfoBlockState: TokenInfoBlockState, + val tokenBalanceBlockState: TokenDetailsBalanceBlockState, ) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 380d2ad51f..bd876c400d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -10,6 +11,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlock import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock @@ -24,8 +26,10 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { .padding(paddingValues = scaffoldPaddings) .padding(horizontal = TangemTheme.dimens.spacing16) .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { TokenInfoBlock(state = state.tokenInfoBlockState) + TokenDetailsBalanceBlock(state = state.tokenBalanceBlockState) } } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt new file mode 100644 index 0000000000..df54b41461 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/TokenDetailsBalanceBlock.kt @@ -0,0 +1,136 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Surface +import androidx.compose.material3.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 androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.RectangleShimmer +import com.tangem.core.ui.components.buttons.HorizontalActionChips +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState +import com.tangem.features.tokendetails.impl.R + +@Composable +internal fun TokenDetailsBalanceBlock(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) { + Surface( + modifier = modifier.fillMaxWidth(), + shape = TangemTheme.shapes.roundedCornersXMedium, + color = TangemTheme.colors.background.primary, + ) { + Column { + Text( + modifier = Modifier + .padding( + top = TangemTheme.dimens.spacing12, + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + ), + text = stringResource(id = R.string.onboarding_balance_title), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + maxLines = 1, + ) + FiatBalance( + state = state, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing4) + .padding(horizontal = TangemTheme.dimens.spacing12), + ) + CryptoBalance( + state = state, + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing4) + .padding(horizontal = TangemTheme.dimens.spacing12), + ) + + HorizontalActionChips( + buttons = state.actionButtons, + modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing12), + ) + } + } +} + +@Composable +private fun FiatBalance(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) { + when (state) { + is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer( + modifier = modifier.size( + width = TangemTheme.dimens.size102, + height = TangemTheme.dimens.size24, + ), + ) + is TokenDetailsBalanceBlockState.Content -> Text( + modifier = modifier, + text = state.fiatBalance, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + ) + is TokenDetailsBalanceBlockState.Error -> Text( + modifier = modifier, + text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + ) + } +} + +@Composable +private fun CryptoBalance(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) { + when (state) { + is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer( + modifier = modifier.size( + width = TangemTheme.dimens.size70, + height = TangemTheme.dimens.size16, + ), + ) + is TokenDetailsBalanceBlockState.Content -> Text( + modifier = modifier, + text = state.cryptoBalance, + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.primary1, + ) + is TokenDetailsBalanceBlockState.Error -> Text( + modifier = modifier, + text = BigDecimalFormatter.EMPTY_BALANCE_SIGN, + style = TangemTheme.typography.caption, + color = TangemTheme.colors.text.primary1, + ) + } +} + +@Preview +@Composable +private fun Preview_TokenDetailsBalanceBlock_LightTheme( + @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, +) { + TangemTheme(isDark = false) { + TokenDetailsBalanceBlock(state) + } +} + +@Preview +@Composable +private fun Preview_TokenDetailsBalanceBlock_DarkTheme( + @PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState, +) { + TangemTheme(isDark = true) { + TokenDetailsBalanceBlock(state) + } +} + +private class TokenDetailsBalanceBlockStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + TokenDetailsPreviewData.balanceLoading, + TokenDetailsPreviewData.balanceContent, + TokenDetailsPreviewData.balanceError, + ), +) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 50e489eedb..bcbf76c58a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -4,12 +4,17 @@ 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.tokendetails.presentation.router.InnerTokenDetailsRouter import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import javax.inject.Inject import kotlin.properties.Delegates +private const val LOADING_DELAY = 4_000L + @HiltViewModel internal class TokenDetailsViewModel @Inject constructor() : ViewModel() { @@ -18,6 +23,14 @@ internal class TokenDetailsViewModel @Inject constructor() : ViewModel() { var uiState by mutableStateOf(getInitialState()) private set + init { + // simulate loading state + viewModelScope.launch { + delay(LOADING_DELAY) + uiState = uiState.copy(tokenBalanceBlockState = TokenDetailsPreviewData.balanceContent) + } + } + private fun getInitialState() = TokenDetailsPreviewData.tokenDetailsState.copy( topAppBarConfig = TokenDetailsPreviewData.tokenDetailsTopAppBarConfig.copy( onBackClick = ::onBackClick, From 66aedc97cbb0066e0da3f848c6c73f6f3a33f8d1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Jul 2023 13:01:32 +0300 Subject: [PATCH 52/58] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Buttons.kt | 14 ++- .../com/tangem/core/ui/components/Notifier.kt | 68 +++++++++++++ .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../com/tangem/core/ui/res/TangemShapes.kt | 2 + .../domain/DefaultSeedPhraseInteractor.kt | 15 ++- .../onboarding/domain/SeedPhraseInteractor.kt | 3 +- .../model/OnboardingSeedPhraseState.kt | 2 +- .../wallet2/ui/ImportSeedPhraseScreen.kt | 97 ++++++++++++++++--- .../ImportSeedPhraseStateBuilder.kt | 15 +-- .../wallet2/viewmodel/SeedPhraseViewModel.kt | 3 +- 10 files changed, 192 insertions(+), 28 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/components/Notifier.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 11682ec1f1..385824799e 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 @@ -230,7 +230,12 @@ private fun PrimaryButtonSample() { verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { PrimaryButton(modifier = Modifier.fillMaxWidth(), text = "Manage tokens", onClick = { }) - PrimaryButton(modifier = Modifier.fillMaxWidth(), showProgress = true, text = "Manage tokens", onClick = { }) + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = true, + text = "Manage tokens", + onClick = { }, + ) PrimaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", @@ -289,7 +294,12 @@ private fun SecondaryButtonSample() { verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), ) { SecondaryButton(modifier = Modifier.fillMaxWidth(), text = "Manage tokens", onClick = { }) - SecondaryButton(modifier = Modifier.fillMaxWidth(), showProgress = true, text = "Manage tokens", onClick = { }) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = true, + text = "Manage tokens", + onClick = { }, + ) SecondaryButtonIconEnd( modifier = Modifier.fillMaxWidth(), text = "Manage tokens", diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt new file mode 100644 index 0000000000..a646f37f33 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Notifier.kt @@ -0,0 +1,68 @@ +package com.tangem.core.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +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.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.res.TangemTheme + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=68%3A20&mode=design&t=WSV3AxC6zV1y0CHF-1) + * */ +@Composable +fun Notifier( + text: String, + modifier: Modifier = Modifier, + textColor: Color = TangemTheme.colors.text.primary1, + backgroundColor: Color = TangemTheme.colors.icon.inactive, +) { + Box( + modifier = modifier + .heightIn(TangemTheme.dimens.size32) + .background( + color = backgroundColor, + shape = TangemTheme.shapes.roundedCorners8, + ), + ) { + Text( + modifier = Modifier + .align(Alignment.Center) + .padding(horizontal = TangemTheme.dimens.size10), + text = text, + color = textColor, + style = TangemTheme.typography.button, + ) + } +} + +@Preview +@Composable +private fun TangemNotifierPreview_Light(@PreviewParameter(NotifierProvider::class) text: String) { + TangemTheme(isDark = false) { + Notifier(text = text) + } +} + +@Preview +@Composable +private fun TangemNotifierPreview_Dark(@PreviewParameter(NotifierProvider::class) text: String) { + TangemTheme(isDark = true) { + Notifier(text = text) + } +} + +private class NotifierProvider : CollectionPreviewParameterProvider( + collection = listOf( + "a", + "Card 1 of 2", + "Card 1 of 2 or many other", + ), +) \ No newline at end of file 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 c3f075ffb0..a78bd58dbd 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 @@ -43,6 +43,7 @@ data class TangemDimens internal constructor( val size5: Dp = 5.dp, val size7: Dp = 7.dp, val size8: Dp = 8.dp, + val size10: Dp = 10.dp, val size11: Dp = 11.dp, val size12: Dp = 12.dp, val size16: Dp = 16.dp, diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt index b452e38004..a3802221fb 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.graphics.Shape data class TangemShapes internal constructor( val roundedCornersSmall: Shape, val roundedCornersSmall2: Shape, + val roundedCorners8: Shape, val roundedCornersMedium: Shape, val roundedCornersXMedium: Shape, val roundedCornersLarge: Shape, @@ -16,6 +17,7 @@ data class TangemShapes internal constructor( constructor(dimens: TangemDimens) : this( roundedCornersSmall = RoundedCornerShape(size = dimens.radius2), roundedCornersSmall2 = RoundedCornerShape(size = dimens.radius4), + roundedCorners8 = RoundedCornerShape(size = dimens.radius8), roundedCornersMedium = RoundedCornerShape(size = dimens.radius12), roundedCornersXMedium = RoundedCornerShape(size = dimens.radius16), roundedCornersLarge = RoundedCornerShape(size = dimens.radius28), diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt index f3152c931e..a63b4bccea 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt @@ -5,6 +5,9 @@ import com.tangem.crypto.bip39.Mnemonic import com.tangem.crypto.bip39.MnemonicErrorResult import com.tangem.feature.onboarding.data.MnemonicRepository import com.tangem.utils.extensions.isNotWhitespace +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList /** [REDACTED_AUTHOR] @@ -58,14 +61,18 @@ internal class DefaultSeedPhraseInteractor constructor( } } - override suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): List { - if (text.isEmpty() || cursorPosition == 0 || hasSelection) return emptyList() + override suspend fun getSuggestions( + text: String, + hasSelection: Boolean, + cursorPosition: Int, + ): ImmutableList { + if (text.isEmpty() || cursorPosition == 0 || hasSelection) return persistentListOf() val word = partWordFinder.getLeadPartOfWord(text, cursorPosition) - ?: return emptyList() + ?: return persistentListOf() val suggestions = repository.getWordsDictionary() .filter { it.startsWith(word, ignoreCase = false) && it != word } - .toList() + .toPersistentList() return suggestions } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt index 43ed7ac068..2c0f319c19 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt @@ -1,6 +1,7 @@ package com.tangem.feature.onboarding.domain import com.tangem.crypto.bip39.Mnemonic +import kotlinx.collections.immutable.ImmutableList /** [REDACTED_AUTHOR] @@ -10,7 +11,7 @@ interface SeedPhraseInteractor { suspend fun getMnemonicComponents(): Result> suspend fun isWordMatch(word: String): Boolean suspend fun validateMnemonicString(text: String): Result> - suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): List + suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): ImmutableList suspend fun insertSuggestionWord(text: String, suggestion: String, cursorPosition: Int): InsertSuggestionResult companion object { diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt index 74b732e86b..44bdac25c3 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt @@ -52,7 +52,7 @@ data class ImportSeedPhraseState( val onSuggestedPhraseClick: (Int) -> Unit, val buttonCreateWallet: ButtonState, val invalidWords: Set = emptySet(), - val suggestionsList: List = emptyList(), + val suggestionsList: ImmutableList = persistentListOf(), val error: SeedPhraseError? = null, ) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index d29fd51838..be8a31f5df 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -1,6 +1,7 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui import androidx.compose.animation.* +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.material.OutlinedTextField @@ -11,10 +12,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset -import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.Notifier import com.tangem.core.ui.components.PrimaryButtonIconStart import com.tangem.core.ui.components.TangemTextFieldsDefault +import com.tangem.core.ui.components.buttons.common.* import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseState @@ -23,6 +29,8 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Onboardi import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.InvalidWordsColorTransformation import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseErrorConverter +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf /** [REDACTED_AUTHOR] @@ -48,7 +56,9 @@ fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Mo state = state, ) SuggestionsBlock( - state = state, + modifier = Modifier.padding(top = TangemTheme.dimens.size4), + suggestionsList = state.suggestionsList, + onClick = { index -> state.onSuggestedPhraseClick(index) }, ) } } @@ -114,25 +124,86 @@ private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modif @Suppress("ReusedModifierInstance") @Composable -private fun SuggestionsBlock(state: ImportSeedPhraseState, modifier: Modifier = Modifier) { +private fun SuggestionsBlock( + suggestionsList: ImmutableList, + onClick: (Int) -> Unit, + modifier: Modifier = Modifier, +) { AnimatedVisibility( enter = fadeIn() + slideIn(initialOffset = { IntOffset(x = 200, y = 0) }), exit = slideOut(targetOffset = { IntOffset(x = -200, y = 0) }) + fadeOut(), - visible = state.suggestionsList.isNotEmpty(), + visible = suggestionsList.isNotEmpty(), ) { LazyRow( - modifier = modifier.fillMaxSize(), + modifier = modifier, contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16), ) { - items(state.suggestionsList.size) { index -> - PrimaryButton( - modifier = Modifier - .height(TangemTheme.dimens.size46) - .padding(all = TangemTheme.dimens.size4), - text = state.suggestionsList[index], - onClick = { state.onSuggestedPhraseClick(index) }, + items(suggestionsList.size) { index -> + SuggestionButton( + modifier = Modifier.rowPadding( + index = index, + rowSize = suggestionsList.size, + outSide = TangemTheme.dimens.size0, + inSide = TangemTheme.dimens.size4, + ), + text = suggestionsList[index], + onClick = { onClick(index) }, ) } } } -} \ No newline at end of file +} + +@Composable +private fun SuggestionButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) { + Box(modifier = modifier.clickable(onClick = onClick)) { + Notifier( + text = text, + backgroundColor = TangemTheme.colors.background.action, + textColor = TangemTheme.colors.text.primary2, + ) + } +} + +private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: Dp): Modifier = when (index) { + 0 -> this.padding(start = outSide, end = inSide) + rowSize - 1 -> this.padding(start = inSide, end = outSide) + else -> this.padding(horizontal = inSide) +} + +@Preview +@Composable +private fun SuggestionsBlockPreview_Light( + @PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList, +) { + TangemTheme(isDark = false) { + SuggestionsBlock( + suggestionsList = suggestions, + onClick = {}, + ) + } +} + +@Preview +@Composable +private fun SuggestionsBlockPreview_Dark( + @PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList, +) { + TangemTheme(isDark = true) { + SuggestionsBlock( + suggestionsList = suggestions, + onClick = {}, + ) + } +} + +private class SuggestionsPreviewParamsProvider : CollectionPreviewParameterProvider>( + collection = listOf( + persistentListOf( + "one", + "each", + "explorer", + "unknown", + ), + ), +) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt index 5ffb9b5c41..794ec1d599 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.text.input.TextFieldValue import com.tangem.feature.onboarding.domain.InsertSuggestionResult import com.tangem.feature.onboarding.domain.SeedPhraseError import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState +import kotlinx.collections.immutable.ImmutableList /** [REDACTED_AUTHOR] @@ -39,12 +40,14 @@ class ImportSeedPhraseStateBuilder { ), ) - fun updateSuggestions(uiState: OnboardingSeedPhraseState, suggestions: List): OnboardingSeedPhraseState = - uiState.copy( - importSeedPhraseState = uiState.importSeedPhraseState.copy( - suggestionsList = suggestions, - ), - ) + fun updateSuggestions( + uiState: OnboardingSeedPhraseState, + suggestions: ImmutableList, + ): OnboardingSeedPhraseState = uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + suggestionsList = suggestions, + ), + ) fun insertSuggestionWord( uiState: OnboardingSeedPhraseState, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 8dcb8ff0d6..c7d9c234ba 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -22,6 +22,7 @@ import com.tangem.utils.coroutines.Debouncer import com.tangem.utils.extensions.isEven import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -373,7 +374,7 @@ class SeedPhraseViewModel @Inject constructor( ) updateUi { val mediateState = uiBuilder.importSeedPhrase.insertSuggestionWord(uiState, insertResult) - uiBuilder.importSeedPhrase.updateSuggestions(mediateState, emptyList()) + uiBuilder.importSeedPhrase.updateSuggestions(mediateState, persistentListOf()) } } } From 5f1a5494dc4f86cbe86bd19128d66bb14438d387 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 15 Jul 2023 01:47:52 +0300 Subject: [PATCH 53/58] Updated on 2026-08-14 --- data/tokens/build.gradle.kts | 1 + .../repository/MockNetworksRepository.kt | 11 +- .../tokens/repository/MockQuotesRepository.kt | 8 +- .../tokens/repository/MockTokensRepository.kt | 18 +- .../com/tangem/domain/core/error/DataError.kt | 9 + .../domain/core/raise/DelegatedRaise.kt | 13 ++ .../domain/tokens/GetTokenListUseCase.kt | 55 ++--- .../domain/tokens/error/TokenListError.kt | 31 +++ .../tangem/domain/tokens/error/TokensError.kt | 21 -- .../TokenListFiatBalanceOperations.kt | 7 +- .../tokens/operations/TokenListOperations.kt | 188 ++++++++++++++++++ .../operations/TokenListSortingOperations.kt | 115 +++++++++++ .../TokenStatusOperations.kt | 16 +- .../TokensStatusesOperations.kt | 74 +++++-- .../tokens/repository/NetworksRepository.kt | 6 +- .../tokens/repository/QuotesRepository.kt | 4 +- .../tokens/repository/TokensRepository.kt | 10 +- .../tokens/utils/TokenListOperations.kt | 163 --------------- .../domain/tokens/GetTokenListUseCaseTest.kt | 59 +++--- .../repository/MockNetworksRepository.kt | 16 +- .../tokens/repository/MockQuotesRepository.kt | 10 +- .../tokens/repository/MockTokensRepository.kt | 25 +-- ...> TokenListErrorToWalletStateConverter.kt} | 8 +- .../wallet/viewmodels/WalletViewModel.kt | 10 +- 24 files changed, 525 insertions(+), 353 deletions(-) create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/error/DataError.kt create mode 100644 domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{utils => operations}/TokenListFiatBalanceOperations.kt (94%) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{utils => operations}/TokenStatusOperations.kt (84%) rename domain/tokens/src/main/kotlin/com/tangem/domain/tokens/{utils => operations}/TokensStatusesOperations.kt (60%) delete mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/{TokenErrorToWalletStateConverter.kt => TokenListErrorToWalletStateConverter.kt} (56%) diff --git a/data/tokens/build.gradle.kts b/data/tokens/build.gradle.kts index 95abf3ed40..2d8aaaf35d 100644 --- a/data/tokens/build.gradle.kts +++ b/data/tokens/build.gradle.kts @@ -5,6 +5,7 @@ plugins { } dependencies { + implementation(projects.domain.core) implementation(projects.domain.tokens) implementation(projects.domain.wallets.models) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt index a328f0a544..26bc58aa70 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockNetworksRepository.kt @@ -1,9 +1,6 @@ package com.tangem.data.tokens.repository -import arrow.core.Either -import arrow.core.right import com.tangem.data.tokens.mock.MockNetworks -import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.Token @@ -14,23 +11,21 @@ import kotlinx.coroutines.flow.flowOf internal class MockNetworksRepository : NetworksRepository { - override fun getNetworks(networksIds: Set): Either> { + override fun getNetworks(networksIds: Set): Set { return MockNetworks.networks .filter { it.id in networksIds } .toSet() - .right() } override fun getNetworkStatuses( userWalletId: UserWalletId, networks: Map>, refresh: Boolean, - ): Flow>> { + ): Flow> { return flowOf( MockNetworks.networksStatuses .filter { it.networkId in networks.keys } - .toSet() - .right(), + .toSet(), ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt index 1e6cbaa1d8..2510893558 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockQuotesRepository.kt @@ -1,9 +1,6 @@ package com.tangem.data.tokens.repository -import arrow.core.Either -import arrow.core.right import com.tangem.data.tokens.mock.MockQuotes -import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Token import com.tangem.domain.tokens.repository.QuotesRepository @@ -12,12 +9,11 @@ import kotlinx.coroutines.flow.flowOf internal class MockQuotesRepository : QuotesRepository { - override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow>> { + override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow> { return flowOf( MockQuotes.quotes .filter { it.tokenId in tokensIds } - .toSet() - .right(), + .toSet(), ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt index 92092f1046..e6b7760a7c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/MockTokensRepository.kt @@ -1,10 +1,6 @@ package com.tangem.data.tokens.repository -import arrow.core.Either -import arrow.core.left -import arrow.core.right import com.tangem.data.tokens.mock.MockTokens -import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Token import com.tangem.domain.tokens.repository.TokensRepository import com.tangem.domain.wallets.models.UserWalletId @@ -18,17 +14,17 @@ internal class MockTokensRepository : TokensRepository { sortedTokensIds: Set, isGrouped: Boolean, isSortedByBalance: Boolean, - ): Either = Unit.right() + ) = Unit - override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> { - return flowOf(value = MockTokens.tokens[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) + override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow> { + return flowOf(value = MockTokens.tokens[userWalletId]!!) } - override fun isTokensGrouped(userWalletId: UserWalletId): Flow> { - return flowOf(MockTokens.isGrouped[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) + override fun isTokensGrouped(userWalletId: UserWalletId): Flow { + return flowOf(value = MockTokens.isGrouped[userWalletId]!!) } - override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> { - return flowOf(MockTokens.isSortedByBalance[userWalletId]?.right() ?: TokensError.EmptyTokens.left()) + override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow { + return flowOf(value = MockTokens.isSortedByBalance[userWalletId]!!) } } \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/error/DataError.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/error/DataError.kt new file mode 100644 index 0000000000..11567ca43b --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/error/DataError.kt @@ -0,0 +1,9 @@ +package com.tangem.domain.core.error + +sealed class DataError : Exception() { + + sealed class NetworkError : DataError() { + + object NoInternetConnection : NetworkError() + } +} \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt new file mode 100644 index 0000000000..ffde660eb6 --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/raise/DelegatedRaise.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.core.raise + +import arrow.core.raise.Raise + +abstract class DelegatedRaise( + private val otherRaise: Raise, + private val transformError: (Error) -> OtherError, +) : Raise { + + override fun raise(r: Error): Nothing { + otherRaise.raise(transformError(r)) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index 024c43ef42..3f42be91fc 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -5,15 +5,14 @@ import arrow.core.left import arrow.core.raise.Raise import arrow.core.raise.recover import arrow.core.right -import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.domain.tokens.operations.TokenListOperations +import com.tangem.domain.tokens.operations.TokensStatusesOperations import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.TokensRepository -import com.tangem.domain.tokens.utils.TokenListFiatBalanceOperations -import com.tangem.domain.tokens.utils.TokenListOperations -import com.tangem.domain.tokens.utils.TokensStatusesOperations import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow @@ -21,13 +20,13 @@ import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.flatMapConcat class GetTokenListUseCase( - private val tokensRepository: TokensRepository, - private val quotesRepository: QuotesRepository, - private val networksRepository: NetworksRepository, - private val dispatchers: CoroutineDispatcherProvider, + internal val tokensRepository: TokensRepository, + internal val quotesRepository: QuotesRepository, + internal val networksRepository: NetworksRepository, + internal val dispatchers: CoroutineDispatcherProvider, ) { - operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow> { + operator fun invoke(userWalletId: UserWalletId, refresh: Boolean = true): Flow> { return channelFlow { recover( block = { @@ -41,53 +40,39 @@ class GetTokenListUseCase( ) } } - private fun Raise.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow { + private fun Raise.getTokenList(userWalletId: UserWalletId, refresh: Boolean): Flow { return getTokensStatuses(userWalletId, refresh).flatMapConcat { tokens -> createTokenList(userWalletId, tokens) } } - private fun Raise.getTokensStatuses( + private fun Raise.getTokensStatuses( userWalletId: UserWalletId, refresh: Boolean, ): Flow> { val operations = TokensStatusesOperations( - tokensRepository, - quotesRepository, - networksRepository, - userWalletId, - refresh, - dispatchers, + userWalletId = userWalletId, + refresh = refresh, + useCase = this@GetTokenListUseCase, raise = this, + transformError = TokenListError::fromTokenStatusesOperations, ) return operations.getTokensStatusesFlow() } - private suspend fun Raise.createTokenList( + private fun Raise.createTokenList( userWalletId: UserWalletId, tokens: Set, ): Flow { - val isAnyTokenLoading = tokens.any { it.value is TokenStatus.Loading } val operations = TokenListOperations( - tokensRepository, - networksRepository, - userWalletId, - calculateFiatBalance(tokens, isAnyTokenLoading), - isAnyTokenLoading, - dispatchers, + userWalletId = userWalletId, + tokens = tokens, + useCase = this@GetTokenListUseCase, raise = this, + transform = TokenListError::fromTokenListOperations, ) - return operations.getTokenListFlow(tokens) - } - - private suspend fun calculateFiatBalance( - tokens: Set, - isAnyTokenLoading: Boolean, - ): TokenList.FiatBalance { - val operations = TokenListFiatBalanceOperations(tokens, isAnyTokenLoading, dispatchers) - - return operations.calculateFiatBalance() + return operations.getTokenListFlow() } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt new file mode 100644 index 0000000000..06897839f6 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt @@ -0,0 +1,31 @@ +package com.tangem.domain.tokens.error + +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.operations.TokenListOperations +import com.tangem.domain.tokens.operations.TokensStatusesOperations + +sealed class TokenListError { + + object EmptyTokens : TokenListError() + + data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : TokenListError() + + data class DataError(val cause: Throwable) : TokenListError() + + internal companion object { + + fun fromTokenStatusesOperations(error: TokensStatusesOperations.Error): TokenListError = when (error) { + is TokensStatusesOperations.Error.DataError -> DataError(error.cause) + is TokensStatusesOperations.Error.EmptyNetworksStatuses, + is TokensStatusesOperations.Error.EmptyQuotes, + is TokensStatusesOperations.Error.EmptyTokens, + -> EmptyTokens + } + + fun fromTokenListOperations(error: TokenListOperations.Error): TokenListError = when (error) { + is TokenListOperations.Error.DataError -> DataError(error.cause) + is TokenListOperations.Error.UnableToSortTokenList -> UnableToSortTokenList(error.unsortedTokenList) + is TokenListOperations.Error.UnableToGroupTokenList -> UnableToSortTokenList(error.ungroupedTokenList) + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt deleted file mode 100644 index 6571bc1d78..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokensError.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.tangem.domain.tokens.error - -import com.tangem.domain.tokens.model.Network -import com.tangem.domain.tokens.model.Token - -sealed class TokensError { - - object EmptyTokens : TokensError() - - object EmptyQuotes : TokensError() - - object EmptyNetworks : TokensError() - - object EmptyNetworkStatues : TokensError() - - data class TokenAmountNotFound(val tokenId: Token.ID) : TokensError() - - data class TokenFiatAmountLessThenZero(val tokenId: Token.ID) : TokensError() - - data class NetworkNotFound(val networkId: Network.ID) : TokensError() -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt similarity index 94% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt index 6d3360512e..69378510f3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListFiatBalanceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFiatBalanceOperations.kt @@ -1,5 +1,6 @@ -package com.tangem.domain.tokens.utils +package com.tangem.domain.tokens.operations +import arrow.core.NonEmptySet import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenStatus import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -7,7 +8,7 @@ import kotlinx.coroutines.withContext import java.math.BigDecimal internal class TokenListFiatBalanceOperations( - private val tokens: Set, + private val tokens: NonEmptySet, private val isAnyTokenLoading: Boolean, private val dispatcher: CoroutineDispatcherProvider, ) { @@ -15,7 +16,7 @@ internal class TokenListFiatBalanceOperations( suspend fun calculateFiatBalance(): TokenList.FiatBalance { return withContext(dispatcher.single) { var fiatBalance: TokenList.FiatBalance = TokenList.FiatBalance.Loading - if (tokens.isEmpty() || isAnyTokenLoading) return@withContext fiatBalance + if (isAnyTokenLoading) return@withContext fiatBalance for (token in tokens) { when (val status = token.value) { diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt new file mode 100644 index 0000000000..87253155db --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -0,0 +1,188 @@ +package com.tangem.domain.tokens.operations + +import arrow.core.NonEmptySet +import arrow.core.raise.Raise +import arrow.core.raise.catch +import arrow.core.raise.ensureNotNull +import arrow.core.toNonEmptySetOrNull +import com.tangem.domain.core.raise.DelegatedRaise +import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.domain.tokens.repository.NetworksRepository +import com.tangem.domain.tokens.repository.TokensRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.withContext + +@Suppress("LongParameterList") +internal class TokenListOperations( + private val tokensRepository: TokensRepository, + private val networksRepository: NetworksRepository, + private val userWalletId: UserWalletId, + private val tokens: Set, + private val dispatchers: CoroutineDispatcherProvider, + raise: Raise, + transform: (Error) -> E, +) : DelegatedRaise(raise, transform) { + + constructor( + userWalletId: UserWalletId, + tokens: Set, + useCase: GetTokenListUseCase, + raise: Raise, + transform: (Error) -> E, + ) : this( + tokensRepository = useCase.tokensRepository, + networksRepository = useCase.networksRepository, + userWalletId = userWalletId, + tokens = tokens, + dispatchers = useCase.dispatchers, + raise = raise, + transform = transform, + ) + + fun getTokenListFlow(): Flow { + return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance -> + createTokenList(isGrouped, isSortedByBalance) + } + } + + private suspend fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList { + return withContext(dispatchers.single) { + val tokensNes = tokens.toNonEmptySetOrNull() + ?: return@withContext TokenList.NotInitialized + + val isAnyTokenLoading = tokensNes.any { it.value is TokenStatus.Loading } + val fiatBalanceOperations = TokenListFiatBalanceOperations(tokensNes, isAnyTokenLoading, dispatchers) + + createTokenList( + tokens = tokensNes, + fiatBalance = fiatBalanceOperations.calculateFiatBalance(), + isAnyTokenLoading = isAnyTokenLoading, + isGrouped = isGrouped, + isSortedByBalance = isSortedByBalance, + ) + } + } + + private suspend fun createTokenList( + tokens: NonEmptySet, + fiatBalance: TokenList.FiatBalance, + isAnyTokenLoading: Boolean, + isGrouped: Boolean, + isSortedByBalance: Boolean, + ): TokenList { + val sortingOperations = TokenListSortingOperations( + tokens = tokens, + isAnyTokenLoading = isAnyTokenLoading, + isSortedByBalance = isSortedByBalance, + dispatchers = dispatchers, + raise = this, + transformError = { e -> + Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) } + }, + ) + + return createTokenList(tokens, sortingOperations, fiatBalance, isGrouped) + } + + private suspend fun createTokenList( + tokens: NonEmptySet, + sortingOperations: TokenListSortingOperations<*>, + fiatBalance: TokenList.FiatBalance, + isGrouped: Boolean, + ): TokenList { + return if (isGrouped) { + val networks = ensureNotNull(getNetworks(tokens).toNonEmptySetOrNull()) { + Error.UnableToGroupTokenList( + ungroupedTokenList = createUngroupedTokenList(sortingOperations, fiatBalance), + ) + } + + createGroupedTokenList(sortingOperations, fiatBalance, networks) + } else { + createUngroupedTokenList(sortingOperations, fiatBalance) + } + } + + private suspend fun getNetworks(tokensNes: NonEmptySet): Set { + return withContext(dispatchers.io) { + val networksIds = tokensNes.map { it.networkId }.toNonEmptySet() + catch( + block = { networksRepository.getNetworks(networksIds) }, + catch = { raise(Error.DataError(it)) }, + ) + } + } + + private suspend fun createUngroupedTokenList( + sortingOperations: TokenListSortingOperations<*>, + fiatBalance: TokenList.FiatBalance, + ): TokenList.Ungrouped = TokenList.Ungrouped( + sortedBy = sortingOperations.getSortType(), + totalFiatBalance = fiatBalance, + tokens = sortingOperations.getTokens(), + ) + + private suspend fun createGroupedTokenList( + sortingOperations: TokenListSortingOperations<*>, + fiatBalance: TokenList.FiatBalance, + networks: NonEmptySet, + ): TokenList.GroupedByNetwork = TokenList.GroupedByNetwork( + sortedBy = sortingOperations.getSortType(), + totalFiatBalance = fiatBalance, + groups = sortingOperations.getGroupedTokens(networks), + ) + + private fun createUnsortedUngroupedTokenList( + tokens: NonEmptySet, + fiatBalance: TokenList.FiatBalance, + ): TokenList.Ungrouped { + return TokenList.Ungrouped( + sortedBy = TokenList.SortType.NONE, + totalFiatBalance = fiatBalance, + tokens = tokens, + ) + } + + private fun getIsGrouped(): Flow { + return tokensRepository.isTokensGrouped(userWalletId) + .catch { raise(Error.DataError(it)) } + .onEmpty { emit(value = false) } + .flowOn(dispatchers.io) + } + + private fun getIsSortedByBalance(): Flow { + return tokensRepository.isTokensSortedByBalance(userWalletId) + .catch { raise(Error.DataError(it)) } + .onEmpty { emit(value = false) } + .flowOn(dispatchers.io) + } + + sealed class Error { + + data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : Error() + + data class UnableToGroupTokenList(val ungroupedTokenList: TokenList.Ungrouped) : Error() + + data class DataError(val cause: Throwable) : Error() + + internal companion object { + + fun fromTokenListOperations( + e: TokenListSortingOperations.Error, + createUnsortedUngroupedTokenList: () -> TokenList.Ungrouped, + ): Error = when (e) { + is TokenListSortingOperations.Error.EmptyNetworks, + is TokenListSortingOperations.Error.EmptyTokens, + is TokenListSortingOperations.Error.NetworkNotFound, + -> UnableToSortTokenList( + unsortedTokenList = createUnsortedUngroupedTokenList(), + ) + } + } + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt new file mode 100644 index 0000000000..5d1b4516bb --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt @@ -0,0 +1,115 @@ +package com.tangem.domain.tokens.operations + +import arrow.core.NonEmptySet +import arrow.core.raise.Raise +import arrow.core.raise.ensure +import arrow.core.raise.ensureNotNull +import arrow.core.toNonEmptySetOrNull +import com.tangem.domain.core.raise.DelegatedRaise +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.model.NetworkGroup +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import java.math.BigDecimal + +internal class TokenListSortingOperations( + private val tokens: Set, + private val isAnyTokenLoading: Boolean, + private val isSortedByBalance: Boolean, + private val dispatchers: CoroutineDispatcherProvider, + raise: Raise, + transformError: (Error) -> E, +) : DelegatedRaise(raise, transformError) { + + suspend fun getGroupedTokens(networks: Set): NonEmptySet { + return withContext(dispatchers.single) { + ensure(tokens.isNotEmpty()) { Error.EmptyTokens } + val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) { + Error.EmptyNetworks + } + + if (isSortedByBalance) { + groupAndSortTokensByBalance(networksNes) + } else { + groupTokens(networksNes) + } + } + } + + suspend fun getTokens(): NonEmptySet { + return withContext(dispatchers.single) { + val tokensNes = ensureNotNull(tokens.toNonEmptySetOrNull()) { + Error.EmptyTokens + } + + if (isSortedByBalance) sortTokensByBalance(tokensNes) else tokensNes + } + } + + fun getSortType() = if (isSortedByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE + + private fun groupTokens(networks: NonEmptySet): NonEmptySet { + val groupedTokens = tokens + .groupBy { it.networkId } + .map { (networkId, tokens) -> + val network = ensureNotNull(networks.firstOrNull { it.id == networkId }) { + Error.NetworkNotFound(networkId) + } + + NetworkGroup( + networkId = network.id, + name = network.name, + tokens = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens }, + ) + } + .toNonEmptySetOrNull() + + return ensureNotNull(groupedTokens) { Error.EmptyTokens } + } + + private fun groupAndSortTokensByBalance(networks: NonEmptySet): NonEmptySet { + val groupsWithSortedTokens = groupTokens(networks) + .map { group -> + val tokens = group.tokens as? NonEmptySet + ?: error("Tokens can not be empty here") + group.copy(tokens = sortTokensByBalance(tokens)) + } + .toNonEmptySet() + + return if (isAnyTokenLoading) { + groupsWithSortedTokens + } else { + sortGroupsByBalance(groupsWithSortedTokens) + } + } + + private fun sortTokensByBalance(tokens: NonEmptySet): NonEmptySet { + return if (isAnyTokenLoading) { + tokens + } else { + tokens.sortedByDescending { it.value.fiatAmount ?: BigDecimal.ZERO } + .toNonEmptySetOrNull() + ?: error("Tokens can not be empty here") + } + } + + private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptySet): NonEmptySet { + return groupsWithSortedTokens + .sortedByDescending { group -> + group.tokens.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO } + } + .toNonEmptySetOrNull() + ?: error("Tokens can not be empty here") + } + + sealed class Error { + + object EmptyTokens : Error() + + object EmptyNetworks : Error() + + data class NetworkNotFound(val networkId: Network.ID) : Error() + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt similarity index 84% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt index c0ebeeafa7..072b541737 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt @@ -1,9 +1,5 @@ -package com.tangem.domain.tokens.utils +package com.tangem.domain.tokens.operations -import arrow.core.raise.Raise -import arrow.core.raise.ensure -import arrow.core.raise.ensureNotNull -import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Token @@ -17,8 +13,7 @@ internal class TokenStatusOperations( private val quote: Quote?, private val networkStatus: NetworkStatus?, private val dispatchers: CoroutineDispatcherProvider, - raise: Raise, -) : Raise by raise { +) { suspend fun createTokenStatus(): TokenStatus = withContext(dispatchers.single) { TokenStatus( @@ -68,7 +63,7 @@ internal class TokenStatusOperations( private fun getTokenAmount(): BigDecimal { val amount = networkStatus?.value?.amounts?.get(token.id) - return ensureNotNull(amount) { TokensError.TokenAmountNotFound(token.id) } + return amount ?: error("Incorrect network status: $networkStatus") } private fun calculateFiatAmountOrNull(amount: BigDecimal, fiatRate: BigDecimal?): BigDecimal? { @@ -78,9 +73,6 @@ internal class TokenStatusOperations( } private fun calculateFiatAmount(amount: BigDecimal, fiatRate: BigDecimal): BigDecimal { - val fiatAmount = amount * fiatRate - ensure(condition = fiatAmount >= BigDecimal.ZERO) { TokensError.TokenFiatAmountLessThenZero(token.id) } - - return fiatAmount + return amount * fiatRate } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt similarity index 60% rename from domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt rename to domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt index 0c90a3c80b..e5dfd69fa0 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokensStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt @@ -1,9 +1,10 @@ -package com.tangem.domain.tokens.utils +package com.tangem.domain.tokens.operations import arrow.core.NonEmptySet import arrow.core.raise.Raise import arrow.core.toNonEmptySetOrNull -import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.core.raise.DelegatedRaise +import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository @@ -14,15 +15,33 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext @Suppress("LongParameterList") -internal class TokensStatusesOperations( +internal class TokensStatusesOperations( private val tokensRepository: TokensRepository, private val quotesRepository: QuotesRepository, private val networksRepository: NetworksRepository, private val userWalletId: UserWalletId, private val refresh: Boolean, private val dispatchers: CoroutineDispatcherProvider, - raise: Raise, -) : Raise by raise { + raise: Raise, + transformError: (Error) -> E, +) : DelegatedRaise(raise, transformError) { + + constructor( + userWalletId: UserWalletId, + refresh: Boolean, + useCase: GetTokenListUseCase, + raise: Raise, + transformError: (Error) -> E, + ) : this( + tokensRepository = useCase.tokensRepository, + quotesRepository = useCase.quotesRepository, + networksRepository = useCase.networksRepository, + userWalletId = userWalletId, + refresh = refresh, + dispatchers = useCase.dispatchers, + raise = raise, + transformError = transformError, + ) fun getTokensStatusesFlow(): Flow> { return getTokens().flatMapConcat { @@ -46,37 +65,43 @@ internal class TokensStatusesOperations( quotes: Set, networkStatuses: Set, ): Set = withContext(dispatchers.single) { - tokens.map { token -> + tokens.mapTo(hashSetOf()) { token -> val quote = quotes.firstOrNull { it.tokenId == token.id } val networkStatus = networkStatuses.firstOrNull { it.networkId == token.networkId } createStatus(token, quote, networkStatus) - }.toSet() + } } private suspend fun createStatus(token: Token, quote: Quote?, networkStatus: NetworkStatus?): TokenStatus { - val operations = TokenStatusOperations(token, quote, networkStatus, dispatchers, raise = this) - return operations.createTokenStatus() + val tokenStatusOperations = TokenStatusOperations( + token = token, + quote = quote, + networkStatus = networkStatus, + dispatchers = dispatchers, + ) + + return tokenStatusOperations.createTokenStatus() } private fun getTokens(): Flow> { return tokensRepository.getTokens(userWalletId, refresh) - .onEmpty { raise(TokensError.EmptyTokens) } - .map { it.bind() } + .catch { raise(Error.DataError(it)) } + .onEmpty { raise(Error.EmptyTokens) } .flowOn(dispatchers.io) } private fun getQuotes(tokensIds: NonEmptySet): Flow> { return quotesRepository.getQuotes(tokensIds, refresh) - .onEmpty { raise(TokensError.EmptyQuotes) } - .map { it.bind() } + .catch { raise(Error.DataError(it)) } + .onEmpty { raise(Error.EmptyQuotes) } .flowOn(dispatchers.io) } private fun getNetworksStatues(groupedTokens: Map>): Flow> { return networksRepository.getNetworkStatuses(userWalletId, groupedTokens, refresh) - .onEmpty { raise(TokensError.EmptyNetworkStatues) } - .map { it.bind() } + .catch { raise(Error.DataError(it)) } + .onEmpty { raise(Error.EmptyNetworksStatuses) } .flowOn(dispatchers.io) } @@ -85,10 +110,21 @@ internal class TokensStatusesOperations( tokens .groupBy { it.networkId } .mapValues { (_, tokens) -> - tokens.toNonEmptySetOrNull() - ?.map { it.id } - ?.toNonEmptySet() - ?: raise(TokensError.EmptyTokens) + // Can not be empty + tokens.toNonEmptySetOrNull()!! + .map { it.id } + .toNonEmptySet() } } + + sealed class Error { + + object EmptyTokens : Error() + + object EmptyQuotes : Error() + + object EmptyNetworksStatuses : Error() + + data class DataError(val cause: Throwable) : Error() + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index df231dd283..93f7f7c95b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -1,7 +1,5 @@ package com.tangem.domain.tokens.repository -import arrow.core.Either -import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.Token @@ -16,11 +14,11 @@ import kotlinx.coroutines.flow.Flow * */ interface NetworksRepository { - fun getNetworks(networksIds: Set): Either> + fun getNetworks(networksIds: Set): Set fun getNetworkStatuses( userWalletId: UserWalletId, networks: Map>, refresh: Boolean, - ): Flow>> + ): Flow> } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt index 314c1d1177..984ffb6608 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/QuotesRepository.kt @@ -1,7 +1,5 @@ package com.tangem.domain.tokens.repository -import arrow.core.Either -import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Token import kotlinx.coroutines.flow.Flow @@ -14,5 +12,5 @@ import kotlinx.coroutines.flow.Flow * */ interface QuotesRepository { - fun getQuotes(tokensIds: Set, refresh: Boolean): Flow>> + fun getQuotes(tokensIds: Set, refresh: Boolean): Flow> } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt index 4b1ed804c3..b544ecb8f5 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/TokensRepository.kt @@ -1,7 +1,5 @@ package com.tangem.domain.tokens.repository -import arrow.core.Either -import com.tangem.domain.tokens.error.TokensError import com.tangem.domain.tokens.model.Token import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -19,11 +17,11 @@ interface TokensRepository { sortedTokensIds: Set, isGrouped: Boolean, isSortedByBalance: Boolean, - ): Either + ) - fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> + fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow> - fun isTokensGrouped(userWalletId: UserWalletId): Flow> + fun isTokensGrouped(userWalletId: UserWalletId): Flow - fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> + fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt deleted file mode 100644 index f5d8bf0d5c..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/utils/TokenListOperations.kt +++ /dev/null @@ -1,163 +0,0 @@ -package com.tangem.domain.tokens.utils - -import arrow.core.NonEmptySet -import arrow.core.raise.Raise -import arrow.core.raise.ensureNotNull -import arrow.core.toNonEmptySetOrNull -import com.tangem.domain.tokens.error.TokensError -import com.tangem.domain.tokens.model.Network -import com.tangem.domain.tokens.model.NetworkGroup -import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.tokens.model.TokenStatus -import com.tangem.domain.tokens.repository.NetworksRepository -import com.tangem.domain.tokens.repository.TokensRepository -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.withContext -import java.math.BigDecimal - -@Suppress("LongParameterList") -internal class TokenListOperations( - private val tokensRepository: TokensRepository, - private val networksRepository: NetworksRepository, - private val userWalletId: UserWalletId, - private val totalFiatBalance: TokenList.FiatBalance, - private val isAnyTokenLoading: Boolean, - private val dispatchers: CoroutineDispatcherProvider, - raise: Raise, -) : Raise by raise { - - fun getTokenListFlow(tokens: Set): Flow { - return combine(getIsGrouped(), getIsSortedByBalance()) { isGrouped, isSortedByBalance -> - createTokenList(tokens, isGrouped, isSortedByBalance) - } - } - - private suspend fun createTokenList( - tokens: Set, - isGrouped: Boolean, - isSortedByBalance: Boolean, - ): TokenList = withContext(dispatchers.single) { - val tokensNes = tokens.toNonEmptySetOrNull() - - when { - tokensNes == null -> TokenList.NotInitialized - isGrouped -> { - val networks = getNetworks(tokensNes) - - createGroupedTokenList(tokensNes, networks, isSortedByBalance) - } - else -> createUngroupedTokenList(tokensNes, isSortedByBalance) - } - } - - private fun createUngroupedTokenList( - tokens: NonEmptySet, - isSortedByBalance: Boolean, - ): TokenList.Ungrouped { - return TokenList.Ungrouped( - sortedBy = getSortType(isSortedByBalance), - totalFiatBalance = totalFiatBalance, - tokens = if (isSortedByBalance) sortTokensByBalance(tokens) else tokens, - ) - } - - private fun createGroupedTokenList( - tokens: NonEmptySet, - networks: NonEmptySet, - isSortedByBalance: Boolean, - ): TokenList.GroupedByNetwork { - return TokenList.GroupedByNetwork( - sortedBy = getSortType(isSortedByBalance), - totalFiatBalance = totalFiatBalance, - groups = if (isSortedByBalance) groupAndSortTokens(tokens, networks) else groupTokens(tokens, networks), - ) - } - - private fun groupAndSortTokens( - tokens: NonEmptySet, - networks: NonEmptySet, - ): NonEmptySet { - val groupsWithSortedTokens = groupTokens(tokens, networks) - .map { group -> - group.copy(tokens = sortTokensByBalance(group.tokens as NonEmptySet)) - } - .toNonEmptySet() - val sortedGroups = if (isAnyTokenLoading) { - groupsWithSortedTokens - } else { - sortGroupsByBalance(groupsWithSortedTokens) - } - - return sortedGroups - } - - private fun sortTokensByBalance(tokens: NonEmptySet): NonEmptySet { - val sortedTokens = if (isAnyTokenLoading) { - tokens - } else { - tokens - .sortedByDescending { it.value.fiatAmount ?: BigDecimal.ZERO } - .toNonEmptySetOrNull() - } - - return sortedTokens!! - } - - private fun groupTokens( - tokens: NonEmptySet, - networks: NonEmptySet, - ): NonEmptySet { - val groups = tokens - .groupBy { it.networkId } - .map { (networkId, tokens) -> - val network = ensureNotNull(networks.firstOrNull { it.id == networkId }) { - TokensError.NetworkNotFound(networkId) - } - - NetworkGroup( - networkId = network.id, - name = network.name, - tokens = tokens.toNonEmptySetOrNull()!!, - ) - } - .toNonEmptySetOrNull() - - return groups!! - } - - private suspend fun getNetworks(tokens: NonEmptySet): NonEmptySet { - return withContext(dispatchers.io) { - val networksIds = tokens.map { it.networkId }.toNonEmptySet() - val networks = networksRepository.getNetworks(networksIds).bind() - ensureNotNull(networks.toNonEmptySetOrNull()) { TokensError.EmptyNetworks } - } - } - - private fun getIsGrouped(): Flow { - return tokensRepository.isTokensGrouped(userWalletId) - .map { it.bind() } - .flowOn(dispatchers.io) - } - - private fun getIsSortedByBalance(): Flow { - return tokensRepository.isTokensSortedByBalance(userWalletId) - .map { it.bind() } - .flowOn(dispatchers.io) - } - - private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptySet): NonEmptySet { - return groupsWithSortedTokens - .sortedByDescending { group -> - group.tokens.sumOf { it.value.fiatAmount ?: BigDecimal.ZERO } - } - .toNonEmptySetOrNull()!! - } - - private fun getSortType(isSortedByBalance: Boolean) = - if (isSortedByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE -} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt index e876c1924d..fb26a27895 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt @@ -3,7 +3,8 @@ package com.tangem.domain.tokens import arrow.core.Either import arrow.core.left import arrow.core.right -import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.core.error.DataError +import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.mock.MockNetworks import com.tangem.domain.tokens.mock.MockQuotes import com.tangem.domain.tokens.mock.MockTokenLists @@ -18,12 +19,10 @@ import com.tangem.domain.tokens.repository.MockTokensRepository import com.tangem.domain.wallets.models.UserWalletId import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider import junit.framework.TestCase.assertEquals -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) internal class GetTokenListUseCaseTest { private val dispatchers = TestingCoroutineDispatcherProvider() @@ -49,9 +48,9 @@ internal class GetTokenListUseCaseTest { @Test fun `when tokens getting failed then error should be received`() = runTest { // Given - val expectedResult = TokensError.EmptyTokens.left() + val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - val useCase = getUseCase(tokens = flowOf(expectedResult)) + val useCase = getUseCase(tokens = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When val result = useCase(userWalletId).first() @@ -63,9 +62,9 @@ internal class GetTokenListUseCaseTest { @Test fun `when quotes getting failed then error should be received`() = runTest { // Given - val expectedResult = TokensError.EmptyQuotes.left() + val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - val useCase = getUseCase(quotes = flowOf(expectedResult)) + val useCase = getUseCase(quotes = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When val result = useCase(userWalletId).first() @@ -77,10 +76,10 @@ internal class GetTokenListUseCaseTest { @Test fun `when networks getting failed and list is groped then error should be received`() = runTest { // Given - val expectedResult = TokensError.EmptyNetworkStatues.left() + val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() val useCase = getUseCase( - networks = expectedResult, + networks = DataError.NetworkError.NoInternetConnection.left(), isGrouped = flowOf(true.right()), ) @@ -94,9 +93,9 @@ internal class GetTokenListUseCaseTest { @Test fun `when networks statuses getting failed then error should be received`() = runTest { // Given - val expectedResult = TokensError.EmptyNetworkStatues.left() + val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - val useCase = getUseCase(statuses = flowOf(expectedResult)) + val useCase = getUseCase(statuses = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When val result = useCase(userWalletId).first() @@ -108,9 +107,9 @@ internal class GetTokenListUseCaseTest { @Test fun `when grouping type getting failed then error should be received`() = runTest { // Given - val expectedResult = TokensError.EmptyTokens.left() + val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - val useCase = getUseCase(isGrouped = flowOf(expectedResult)) + val useCase = getUseCase(isGrouped = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When val result = useCase(userWalletId).first() @@ -122,9 +121,9 @@ internal class GetTokenListUseCaseTest { @Test fun `when sorting type getting failed then error should be received`() = runTest { // Given - val expectedResult = TokensError.EmptyTokens.left() + val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - val useCase = getUseCase(isSortedByBalance = flowOf(expectedResult)) + val useCase = getUseCase(isSortedByBalance = flowOf(DataError.NetworkError.NoInternetConnection.left())) // When val result = useCase(userWalletId).first() @@ -136,10 +135,10 @@ internal class GetTokenListUseCaseTest { @Test fun `when tokens getting failed on second emit then error should be received`() = runTest { // Given - val error = TokensError.EmptyTokens.left() + val error = DataError.NetworkError.NoInternetConnection.left() val expectedResult = listOf( MockTokenLists.ungroupedTokenList.right(), - error, + TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left(), ) val useCase = getUseCase( @@ -173,10 +172,10 @@ internal class GetTokenListUseCaseTest { @Test fun `when list is grouped and networks getting failed then error should be received`() = runTest { - val expectedResult = TokensError.EmptyNetworks.left() + val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() val useCase = getUseCase( - networks = expectedResult, + networks = DataError.NetworkError.NoInternetConnection.left(), isGrouped = flowOf(true.right()), ) @@ -235,8 +234,8 @@ internal class GetTokenListUseCaseTest { } @Test - fun `when networks is empty and list is grouped then error should be received`() = runTest { - val expectedResult = TokensError.EmptyNetworks.left() + fun `when networks is empty and list is grouped then ungrouped list should be received`() = runTest { + val expectedResult = TokenListError.UnableToSortTokenList(MockTokenLists.ungroupedTokenList).left() val useCase = getUseCase( networks = emptySet().right(), @@ -252,7 +251,7 @@ internal class GetTokenListUseCaseTest { @Test fun `when tokens flow is empty then error should be received`() = runTest { - val expectedResult = TokensError.EmptyTokens.left() + val expectedResult = TokenListError.EmptyTokens.left() val useCase = getUseCase(tokens = flowOf()) @@ -265,7 +264,7 @@ internal class GetTokenListUseCaseTest { @Test fun `when networks statuses flow is empty then error should be received`() = runTest { - val expectedResult = TokensError.EmptyNetworkStatues.left() + val expectedResult = TokenListError.EmptyTokens.left() val useCase = getUseCase(statuses = flowOf()) @@ -291,7 +290,7 @@ internal class GetTokenListUseCaseTest { @Test fun `when quotes flow is empty then error should be received`() = runTest { - val expectedResult = TokensError.EmptyQuotes.left() + val expectedResult = TokenListError.EmptyTokens.left() val useCase = getUseCase(quotes = flowOf()) @@ -319,12 +318,12 @@ internal class GetTokenListUseCaseTest { } private fun getUseCase( - tokens: Flow>> = flowOf(MockTokens.tokens.right()), - quotes: Flow>> = flowOf(MockQuotes.quotes.right()), - networks: Either> = MockNetworks.networks.right(), - statuses: Flow>> = flowOf(MockNetworks.errorNetworksStatuses.right()), - isGrouped: Flow> = flowOf(MockTokenLists.isGrouped.right()), - isSortedByBalance: Flow> = flowOf(MockTokenLists.isSortedByBalance.right()), + tokens: Flow>> = flowOf(MockTokens.tokens.right()), + quotes: Flow>> = flowOf(MockQuotes.quotes.right()), + networks: Either> = MockNetworks.networks.right(), + statuses: Flow>> = flowOf(MockNetworks.errorNetworksStatuses.right()), + isGrouped: Flow> = flowOf(MockTokenLists.isGrouped.right()), + isSortedByBalance: Flow> = flowOf(MockTokenLists.isSortedByBalance.right()), ) = GetTokenListUseCase( dispatchers = dispatchers, tokensRepository = MockTokensRepository(tokens, isGrouped, isSortedByBalance), diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt index 3267a30ba2..98c7002327 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -1,27 +1,29 @@ package com.tangem.domain.tokens.repository import arrow.core.Either -import com.tangem.domain.tokens.error.TokensError +import arrow.core.getOrElse +import com.tangem.domain.core.error.DataError import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.model.Token import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map internal class MockNetworksRepository( - private val networks: Either>, - private val statuses: Flow>>, + private val networks: Either>, + private val statuses: Flow>>, ) : NetworksRepository { - override fun getNetworks(networksIds: Set): Either> { - return networks + override fun getNetworks(networksIds: Set): Set { + return networks.getOrElse { throw it } } override fun getNetworkStatuses( userWalletId: UserWalletId, networks: Map>, refresh: Boolean, - ): Flow>> { - return statuses + ): Flow> { + return statuses.map { it.getOrElse { e -> throw e } } } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt index e82b0c0c82..fcf52e44e4 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockQuotesRepository.kt @@ -1,16 +1,18 @@ package com.tangem.domain.tokens.repository import arrow.core.Either -import com.tangem.domain.tokens.error.TokensError +import arrow.core.getOrElse +import com.tangem.domain.core.error.DataError import com.tangem.domain.tokens.model.Quote import com.tangem.domain.tokens.model.Token import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map internal class MockQuotesRepository( - private val quotes: Flow>>, + private val quotes: Flow>>, ) : QuotesRepository { - override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow>> { - return quotes + override fun getQuotes(tokensIds: Set, refresh: Boolean): Flow> { + return quotes.map { it.getOrElse { e -> throw e } } } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt index 5a7a757179..813816c52a 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockTokensRepository.kt @@ -1,16 +1,17 @@ package com.tangem.domain.tokens.repository import arrow.core.Either -import arrow.core.right -import com.tangem.domain.tokens.error.TokensError +import arrow.core.getOrElse +import com.tangem.domain.core.error.DataError import com.tangem.domain.tokens.model.Token import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map internal class MockTokensRepository( - private val tokens: Flow>>, - private val isGrouped: Flow>, - private val isSortedByBalance: Flow>, + private val tokens: Flow>>, + private val isGrouped: Flow>, + private val isSortedByBalance: Flow>, ) : TokensRepository { override suspend fun sortTokens( @@ -18,17 +19,17 @@ internal class MockTokensRepository( sortedTokensIds: Set, isGrouped: Boolean, isSortedByBalance: Boolean, - ): Either = Unit.right() + ) = Unit - override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow>> { - return tokens + override fun getTokens(userWalletId: UserWalletId, refresh: Boolean): Flow> { + return tokens.map { it.getOrElse { e -> throw e } } } - override fun isTokensGrouped(userWalletId: UserWalletId): Flow> { - return isGrouped + override fun isTokensGrouped(userWalletId: UserWalletId): Flow { + return isGrouped.map { it.getOrElse { e -> throw e } } } - override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow> { - return isSortedByBalance + override fun isTokensSortedByBalance(userWalletId: UserWalletId): Flow { + return isSortedByBalance.map { it.getOrElse { e -> throw e } } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt similarity index 56% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt index 0f8cb0ca2f..d6f3112be9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenErrorToWalletStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListErrorToWalletStateConverter.kt @@ -1,15 +1,15 @@ package com.tangem.feature.wallet.presentation.wallet.utils -import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.error.TokenListError import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.utils.converter.Converter -internal class TokenErrorToWalletStateConverter( +internal class TokenListErrorToWalletStateConverter( private val currentState: WalletStateHolder, -) : Converter { +) : Converter { // TODO: [REDACTED_JIRA] - override fun convert(value: TokensError): WalletStateHolder { + override fun convert(value: TokenListError): WalletStateHolder { return currentState } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index d47726ddeb..c606c9e404 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -16,7 +16,7 @@ import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.userwallets.UserWalletBuilder import com.tangem.domain.wallets.usecase.SaveWalletUseCase -import com.tangem.domain.tokens.error.TokensError +import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.common.WalletPreviewData @@ -24,7 +24,7 @@ import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens -import com.tangem.feature.wallet.presentation.wallet.utils.TokenErrorToWalletStateConverter +import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorToWalletStateConverter import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel @@ -152,9 +152,9 @@ internal class WalletViewModel @Inject constructor( .launchIn(viewModelScope) } - private fun updateStateWithTokenListOrError(tokenList: Either): WalletStateHolder { - val updateStateWithError = { error: TokensError -> - val converter = TokenErrorToWalletStateConverter(uiState) + private fun updateStateWithTokenListOrError(tokenList: Either): WalletStateHolder { + val updateStateWithError = { error: TokenListError -> + val converter = TokenListErrorToWalletStateConverter(uiState) converter.convert(error) } From 9e7e5f0157f2c692d02bb0af48047e77973e15d3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Jul 2023 16:08:15 +0500 Subject: [PATCH 54/58] Updated on 2026-08-14 --- .../tokendetails/TokenDetailsPreviewData.kt | 14 ++++++++++++++ .../tokendetails/state/TokenDetailsState.kt | 3 +++ .../tokendetails/ui/TokenDetailsScreen.kt | 2 ++ .../viewmodels/TokenDetailsViewModel.kt | 5 ++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt index c94cca8569..5c20a6664d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/TokenDetailsPreviewData.kt @@ -1,6 +1,8 @@ package com.tangem.feature.tokendetails.presentation.tokendetails import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.core.ui.components.marketprice.PriceChangeConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarConfig @@ -71,9 +73,21 @@ internal object TokenDetailsPreviewData { ) val balanceError = TokenDetailsBalanceBlockState.Error(actionButtons = disabledActionButtons) + val marketPriceContent = MarketPriceBlockState.Content( + currencyName = "USDT", + price = "98900 $", + priceChangeConfig = PriceChangeConfig( + valueInPercent = "10.89%", + type = PriceChangeConfig.Type.UP, + ), + ) + + private val marketPriceLoading = MarketPriceBlockState.Loading(currencyName = "USDT") + val tokenDetailsState = TokenDetailsState( topAppBarConfig = tokenDetailsTopAppBarConfig, tokenInfoBlockState = tokenInfoBlockState, tokenBalanceBlockState = balanceLoading, + marketPriceBlockState = marketPriceLoading, ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt index 988dd22bfa..8c7ba02c14 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -1,7 +1,10 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState + data class TokenDetailsState( val topAppBarConfig: TokenDetailsTopAppBarConfig, val tokenInfoBlockState: TokenInfoBlockState, val tokenBalanceBlockState: TokenDetailsBalanceBlockState, + val marketPriceBlockState: MarketPriceBlockState, ) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index bd876c400d..a8a20dfa34 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.marketprice.MarketPriceBlock import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState @@ -30,6 +31,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { ) { TokenInfoBlock(state = state.tokenInfoBlockState) TokenDetailsBalanceBlock(state = state.tokenBalanceBlockState) + MarketPriceBlock(state = state.marketPriceBlockState) } } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index bcbf76c58a..4a75d46fb5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -27,7 +27,10 @@ internal class TokenDetailsViewModel @Inject constructor() : ViewModel() { // simulate loading state viewModelScope.launch { delay(LOADING_DELAY) - uiState = uiState.copy(tokenBalanceBlockState = TokenDetailsPreviewData.balanceContent) + uiState = uiState.copy( + tokenBalanceBlockState = TokenDetailsPreviewData.balanceContent, + marketPriceBlockState = TokenDetailsPreviewData.marketPriceContent, + ) } } From e2ce1e4a9708be853006f27999d8e1f140f60bfb Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Jul 2023 21:59:22 +0800 Subject: [PATCH 55/58] Updated on 2026-08-14 --- .../components/notifications/Notification.kt | 65 +++++-- .../notifications/NotificationState.kt | 34 +++- .../com/tangem/core/ui/res/TangemDimens.kt | 1 + .../presentation/common/WalletPreviewData.kt | 13 +- .../wallet/state/WalletBottomSheetConfig.kt | 79 +++++++++ .../wallet/state/WalletNotification.kt | 120 +++++++++++-- .../wallet/state/WalletStateHolder.kt | 5 + .../presentation/wallet/ui/WalletScreen.kt | 9 +- .../wallet/ui/components/WalletBottomSheet.kt | 161 ++++++++++++++++++ 9 files changed, 448 insertions(+), 39 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletBottomSheetConfig.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletBottomSheet.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index 9d4f2cf480..76b7894881 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -19,6 +19,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH2 +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme @@ -43,10 +45,10 @@ fun Notification(state: NotificationState, modifier: Modifier = Modifier) { ) .clickable( enabled = when (state) { - is NotificationState.Simple -> false - is NotificationState.Action -> true + is NotificationState.Clickable -> true + is NotificationState.Simple, is NotificationState.Closable -> false }, - onClick = if (state is NotificationState.Action) { + onClick = if (state is NotificationState.Clickable) { state.onClick } else { {} @@ -67,12 +69,23 @@ fun Notification(state: NotificationState, modifier: Modifier = Modifier) { ) NotificationInfoBlock( - title = state.title, - subtitle = state.subtitle, + title = state.title.resolveReference(), + subtitle = state.subtitle?.resolveReference(), modifier = Modifier.align(alignment = Alignment.CenterStart), ) - if (state is NotificationState.Action) { + if (state is NotificationState.Closable) { + Icon( + modifier = Modifier + .size(size = TangemTheme.dimens.size20) + .align(alignment = Alignment.TopEnd), + painter = painterResource(id = R.drawable.ic_close_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + + if (state is NotificationState.Clickable) { Icon( modifier = Modifier .size(size = TangemTheme.dimens.size20) @@ -149,30 +162,50 @@ private fun Preview_WarningNotification_Dark( private class NotificationStateProvider : CollectionPreviewParameterProvider( collection = listOf( NotificationState.Simple( - title = "Your wallet hasn’t been backed up", - subtitle = "Lorem ipsum dolor sit amet, consectetur " + - "adipiscing elit, sed do eiusmod tempor incididunt ut labore et...", + title = TextReference.Str(value = "Your wallet hasn’t been backed up"), + subtitle = TextReference.Str( + value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " + + "ut labore et...", + ), iconResId = R.drawable.img_attention_20, ), NotificationState.Simple( - title = "Your wallet hasn’t been backed up", + title = TextReference.Str("Your wallet hasn’t been backed up"), subtitle = null, iconResId = R.drawable.ic_alert_circle_24, tint = TangemColorPalette.Amaranth, ), - NotificationState.Action( - title = "Your wallet hasn’t been backed up", - subtitle = "Lorem ipsum dolor sit amet, consectetur " + - "adipiscing elit, sed do eiusmod tempor incididunt ut labore et...", + NotificationState.Clickable( + title = TextReference.Str(value = "Your wallet hasn’t been backed up"), + subtitle = TextReference.Str( + value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " + + "ut labore et...", + ), iconResId = R.drawable.img_attention_20, onClick = {}, ), - NotificationState.Action( - title = "Your wallet hasn’t been backed up", + NotificationState.Clickable( + title = TextReference.Str(value = "Your wallet hasn’t been backed up"), subtitle = null, iconResId = R.drawable.ic_alert_circle_24, tint = TangemColorPalette.Amaranth, onClick = {}, ), + NotificationState.Closable( + title = TextReference.Str(value = "Your wallet hasn’t been backed up"), + subtitle = TextReference.Str( + value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt " + + "ut labore et...", + ), + iconResId = R.drawable.img_attention_20, + onCloseClick = {}, + ), + NotificationState.Closable( + title = TextReference.Str(value = "Your wallet hasn’t been backed up"), + subtitle = null, + iconResId = R.drawable.ic_alert_circle_24, + tint = TangemColorPalette.Amaranth, + onCloseClick = {}, + ), ), ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationState.kt index 065fa1e125..f787e62a66 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationState.kt @@ -2,6 +2,7 @@ package com.tangem.core.ui.components.notifications import androidx.annotation.DrawableRes import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.extensions.TextReference /** * Notification component state @@ -14,8 +15,8 @@ import androidx.compose.ui.graphics.Color [REDACTED_AUTHOR] */ sealed class NotificationState( - open val title: String, - open val subtitle: String? = null, + open val title: TextReference, + open val subtitle: TextReference? = null, @DrawableRes open val iconResId: Int, open val tint: Color? = null, ) { @@ -29,8 +30,8 @@ sealed class NotificationState( * @property tint icon tint */ data class Simple( - override val title: String, - override val subtitle: String? = null, + override val title: TextReference, + override val subtitle: TextReference? = null, @DrawableRes override val iconResId: Int, override val tint: Color? = null, ) : NotificationState(title, subtitle, iconResId, tint) @@ -42,13 +43,30 @@ sealed class NotificationState( * @property subtitle subtitle * @property iconResId icon resource id * @property tint icon tint - * @param onClick lambda be invoked when notification component is clicked + * @property onClick lambda be invoked when notification component is clicked */ - data class Action( - override val title: String, - override val subtitle: String? = null, + data class Clickable( + override val title: TextReference, + override val subtitle: TextReference? = null, @DrawableRes override val iconResId: Int, override val tint: Color? = null, val onClick: () -> Unit, ) : NotificationState(title, subtitle, iconResId, tint) + + /** + * Closable notification state + * + * @property title title + * @property subtitle subtitle + * @property iconResId icon resource id + * @property tint icon tint + * @property onCloseClick lambda be invoked when close button is clicked + */ + data class Closable( + override val title: TextReference, + override val subtitle: TextReference? = null, + @DrawableRes override val iconResId: Int, + override val tint: Color? = null, + val onCloseClick: (() -> Unit)? = null, + ) : NotificationState(title, subtitle, iconResId, tint) } \ No newline at end of file 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 a78bd58dbd..8e17a060f2 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 @@ -102,6 +102,7 @@ data class TangemDimens internal constructor( val spacing34: Dp = 34.dp, val spacing36: Dp = 34.dp, val spacing38: Dp = 38.dp, + val spacing40: Dp = 40.dp, val spacing44: Dp = 44.dp, val spacing50: Dp = 50.dp, val spacing52: Dp = 52.dp, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index bc2443bc0e..3e1d581e0c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -200,6 +200,15 @@ internal object WalletPreviewData { ), ) + val bottomSheet = WalletBottomSheetConfig( + isShow = false, + onDismissRequest = {}, + content = WalletBottomSheetConfig.BottomSheetContentConfig.UnlockWallets( + onUnlockClick = {}, + onScanClick = {}, + ), + ) + private val manageButtons = persistentListOf( WalletManageButton.Buy(onClick = {}), WalletManageButton.Send(onClick = {}), @@ -268,9 +277,10 @@ internal object WalletPreviewData { notifications = persistentListOf( WalletNotification.UnreachableNetworks, WalletNotification.LikeTangemApp(onClick = {}), - WalletNotification.NeedToBackup(onClick = {}), + WalletNotification.BackupCard(onClick = {}), WalletNotification.ScanCard(onClick = {}), ), + bottomSheet = bottomSheet, onOrganizeTokensClick = {}, ) @@ -303,6 +313,7 @@ internal object WalletPreviewData { ), notifications = persistentListOf(WalletNotification.LikeTangemApp(onClick = {})), buttons = manageButtons.map(WalletManageButton::config).toPersistentList(), + bottomSheet = bottomSheet, marketPriceBlockState = MarketPriceBlockState.Content( currencyName = "BTC", price = "98900.12$", diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletBottomSheetConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletBottomSheetConfig.kt new file mode 100644 index 0000000000..5aafcd4464 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletBottomSheetConfig.kt @@ -0,0 +1,79 @@ +package com.tangem.feature.wallet.presentation.wallet.state + +import androidx.annotation.DrawableRes +import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList +import com.tangem.core.ui.res.TangemColorPalette +import com.tangem.feature.wallet.impl.R + +/** + * Wallet bottom sheet config + * + * @property isShow flag that determine if bottom sheet is shown + * @property onDismissRequest lambda be invoked when bottom sheet is dismissed + * @property content content config + * +[REDACTED_AUTHOR] + */ +// TODO: Finalize notification strings [REDACTED_JIRA] +internal data class WalletBottomSheetConfig( + val isShow: Boolean, + val onDismissRequest: () -> Unit, + val content: BottomSheetContentConfig, +) { + + sealed class BottomSheetContentConfig( + open val title: TextReference, + open val subtitle: TextReference, + @DrawableRes open val iconResId: Int, + open val tint: Color? = null, + val primaryButtonConfig: ButtonConfig, + val secondaryButtonConfig: ButtonConfig? = null, + ) { + + data class ButtonConfig(val text: String, val onClick: () -> Unit, @DrawableRes val iconResId: Int? = null) + + data class UnlockWallets( + val onUnlockClick: () -> Unit, + val onScanClick: () -> Unit, + ) : BottomSheetContentConfig( + title = TextReference.Str(value = "Unlock needed"), + subtitle = TextReference.Str( + value = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + + "incididunt ut labore et dolore magna aliqua.", + ), + iconResId = R.drawable.ic_locked_24, + tint = TangemColorPalette.Black, + primaryButtonConfig = ButtonConfig(text = "Unlock", onClick = onUnlockClick), + secondaryButtonConfig = ButtonConfig( + text = "Scan card", + onClick = onScanClick, + iconResId = R.drawable.ic_tangem_24, + ), + ) + + data class LikeTangemApp( + val onRateTheAppClick: () -> Unit, + val onShareClick: () -> Unit, + ) : BottomSheetContentConfig( + title = TextReference.Str(value = "Like Tangem App?"), + subtitle = TextReference.Str(value = "How was your experience with our app? Let us know:"), + iconResId = R.drawable.ic_star_24, + tint = TangemColorPalette.Tangerine, + primaryButtonConfig = ButtonConfig(text = "Rate the app", onClick = onRateTheAppClick), + secondaryButtonConfig = ButtonConfig(text = "Share feedback", onClick = onShareClick), + ) + + data class MultiWalletAlreadySignedHashes(val onLearnClick: () -> Unit) : BottomSheetContentConfig( + title = TextReference.Res( + id = R.string.warning_important_security_info, + formatArgs = WrappedList(listOf("\u26A0")), + ), + subtitle = TextReference.Res(id = R.string.warning_signed_tx_previously), + iconResId = R.drawable.img_attention_20, + tint = null, + primaryButtonConfig = ButtonConfig(text = "Learn more", onClick = onLearnClick), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt index 39e760dd4f..66079037c6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt @@ -1,6 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.state import com.tangem.core.ui.components.notifications.NotificationState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.WrappedList import com.tangem.core.ui.res.TangemColorPalette import com.tangem.feature.wallet.impl.R @@ -11,26 +13,107 @@ import com.tangem.feature.wallet.impl.R * [REDACTED_AUTHOR] */ +// TODO: Finalize notification strings [REDACTED_JIRA] sealed class WalletNotification(open val state: NotificationState) { + /** Clickable notification */ + sealed interface Clickable { + + /** Lambda be invoked when notification is clicked */ + val onClick: () -> Unit + } + + /** "Development card" notification */ + object DevCard : WalletNotification( + state = NotificationState.Simple( + title = TextReference.Res(id = R.string.common_warning), + subtitle = TextReference.Res(id = R.string.alert_developer_card), + iconResId = R.drawable.ic_alert_circle_24, + tint = TangemColorPalette.Amaranth, + ), + ) + + /** "Test card" notification */ + object TestCard : WalletNotification( + state = NotificationState.Simple( + title = TextReference.Res(id = R.string.common_warning), + subtitle = TextReference.Res(id = R.string.warning_testnet_card_message), + iconResId = R.drawable.ic_alert_circle_24, + tint = TangemColorPalette.Amaranth, + ), + ) + + /** "Demo card" notification */ + object DemoCard : WalletNotification( + state = NotificationState.Simple( + title = TextReference.Res(id = R.string.common_warning), + subtitle = TextReference.Res(id = R.string.alert_demo_message), + iconResId = R.drawable.ic_alert_circle_24, + tint = TangemColorPalette.Amaranth, + ), + ) + + /** "Card verification failed" notification */ + object CardVerificationFailed : WalletNotification( + state = NotificationState.Simple( + title = TextReference.Res(id = R.string.warning_failed_to_verify_card_title), + subtitle = TextReference.Res(id = R.string.warning_failed_to_verify_card_message), + iconResId = R.drawable.ic_alert_circle_24, + tint = TangemColorPalette.Amaranth, + ), + ) + + /** + * "Wallet already signed hashes" notification + * + * @property onClick lambda be invoked when notification is clicked + */ + data class WalletAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification( + state = NotificationState.Clickable( + title = TextReference.Res(id = R.string.common_warning), + subtitle = TextReference.Res(id = R.string.alert_card_signed_transactions), + iconResId = R.drawable.img_attention_20, + onClick = onClick, + tint = null, + ), + ) + + /** + * "Multi wallet already signed hashes" notification + * + * @property onClick lambda be invoked when notification is clicked + */ + data class MultiWalletAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification( + state = NotificationState.Clickable( + title = TextReference.Res( + id = R.string.warning_important_security_info, + formatArgs = WrappedList(listOf("\u26A0")), + ), + subtitle = TextReference.Res(id = R.string.warning_signed_tx_previously), + iconResId = R.drawable.img_attention_20, + onClick = onClick, + tint = null, + ), + ) + /** * "Backup the card" notification * * @property onClick lambda be invoked when notification is clicked */ - data class NeedToBackup(val onClick: () -> Unit) : WalletNotification( - state = NotificationState.Action( - title = "Backup your card", - iconResId = R.drawable.ic_alert_circle_24, + data class BackupCard(override val onClick: () -> Unit) : Clickable, WalletNotification( + state = NotificationState.Clickable( + title = TextReference.Str(value = "Backup your card"), + iconResId = R.drawable.img_attention_20, onClick = onClick, - tint = TangemColorPalette.Amaranth, + tint = null, ), ) /** "Unreachable networks" notification */ object UnreachableNetworks : WalletNotification( state = NotificationState.Simple( - title = "Some networks are unreachable", + title = TextReference.Str(value = "Some networks are unreachable"), iconResId = R.drawable.img_attention_20, tint = null, ), @@ -41,9 +124,9 @@ sealed class WalletNotification(open val state: NotificationState) { * * @property onClick lambda be invoked when notification is clicked */ - data class LikeTangemApp(val onClick: () -> Unit) : WalletNotification( - state = NotificationState.Action( - title = "Like Tangem App?", + data class LikeTangemApp(override val onClick: () -> Unit) : Clickable, WalletNotification( + state = NotificationState.Clickable( + title = TextReference.Str(value = "Like Tangem App?"), iconResId = R.drawable.ic_star_24, onClick = onClick, tint = TangemColorPalette.Tangerine, @@ -55,11 +138,24 @@ sealed class WalletNotification(open val state: NotificationState) { * * @property onClick lambda be invoked when notification is clicked */ - data class ScanCard(val onClick: () -> Unit) : WalletNotification( - state = NotificationState.Action( - title = "Scan your card to continue", + data class ScanCard(override val onClick: () -> Unit) : Clickable, WalletNotification( + state = NotificationState.Clickable( + title = TextReference.Str(value = "Scan your card to continue"), iconResId = R.drawable.ic_tangem_24, onClick = onClick, ), ) + + /** + * "Unlock wallets" notification + * + * @property onClick lambda be invoked when notification is clicked + */ + data class UnlockWallets(override val onClick: () -> Unit) : Clickable, WalletNotification( + state = NotificationState.Clickable( + title = TextReference.Str(value = "Unlock needed"), + iconResId = R.drawable.ic_locked_24, + onClick = onClick, + ), + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index c4b9c37ee8..66785bd0ee 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -23,6 +23,7 @@ internal sealed class WalletStateHolder( open val pullToRefreshConfig: WalletPullToRefreshConfig, open val contentItems: ImmutableList, open val notifications: ImmutableList, + open val bottomSheet: WalletBottomSheetConfig? = null, ) { /** @@ -43,6 +44,7 @@ internal sealed class WalletStateHolder( override val pullToRefreshConfig: WalletPullToRefreshConfig, override val contentItems: ImmutableList, override val notifications: ImmutableList, + override val bottomSheet: WalletBottomSheetConfig, val onOrganizeTokensClick: () -> Unit, ) : WalletStateHolder( onBackClick = onBackClick, @@ -50,6 +52,7 @@ internal sealed class WalletStateHolder( walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig, contentItems = contentItems, + bottomSheet = bottomSheet, notifications = notifications, ) @@ -72,6 +75,7 @@ internal sealed class WalletStateHolder( override val pullToRefreshConfig: WalletPullToRefreshConfig, override val contentItems: ImmutableList, override val notifications: ImmutableList, + override val bottomSheet: WalletBottomSheetConfig, val buttons: ImmutableList, val marketPriceBlockState: MarketPriceBlockState, ) : WalletStateHolder( @@ -81,5 +85,6 @@ internal sealed class WalletStateHolder( pullToRefreshConfig = pullToRefreshConfig, contentItems = contentItems, notifications = notifications, + bottomSheet = bottomSheet, ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 42f5485b26..5f825b106f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -13,16 +13,16 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.buttons.HorizontalActionChips import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.buttons.actions.RoundedActionButton -import com.tangem.core.ui.components.buttons.HorizontalActionChips import com.tangem.core.ui.components.marketprice.MarketPriceBlock import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.transactions.Transaction @@ -34,6 +34,7 @@ import com.tangem.feature.wallet.presentation.common.component.TokenItem import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletBottomSheet import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletTopBar import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.TransactionsBlockGroupTitle @@ -159,6 +160,10 @@ internal fun WalletScreen(state: WalletStateHolder) { ) } } + + state.bottomSheet?.let { bottomSheetConfig -> + if (bottomSheetConfig.isShow) WalletBottomSheet(config = bottomSheetConfig) + } } @Composable diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletBottomSheet.kt new file mode 100644 index 0000000000..45b7a7a752 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletBottomSheet.kt @@ -0,0 +1,161 @@ +package com.tangem.feature.wallet.presentation.wallet.ui.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +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.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.PrimaryButtonIconStart +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.SecondaryButtonIconStart +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletBottomSheetConfig + +/** + * Wallet bottom sheet with detail notification information + * + * @param config component config + * +[REDACTED_AUTHOR] + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun WalletBottomSheet(config: WalletBottomSheetConfig) { + ModalBottomSheet( + onDismissRequest = config.onDismissRequest, + sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), + containerColor = TangemTheme.colors.background.primary, + dragHandle = { BottomSheetDefaults.DragHandle() }, + ) { + BottomSheetContent(config = config.content) + } +} + +@Composable +private fun BottomSheetContent(config: WalletBottomSheetConfig.BottomSheetContentConfig) { + Column( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .padding(top = TangemTheme.dimens.spacing40, bottom = TangemTheme.dimens.spacing16), + verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing40), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val iconTint = config.tint + if (iconTint != null) { + Icon( + painter = painterResource(id = config.iconResId), + contentDescription = null, + modifier = Modifier.size(size = TangemTheme.dimens.size48), + tint = iconTint, + ) + } else { + Image( + painter = painterResource(id = config.iconResId), + contentDescription = null, + modifier = Modifier.size(size = TangemTheme.dimens.size48), + ) + } + + Text( + text = config.title.resolveReference(), + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + style = TangemTheme.typography.h2, + ) + + Text( + text = config.subtitle.resolveReference(), + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + style = TangemTheme.typography.body2, + ) + + Column(verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing10)) { + val buttonModifier = Modifier.fillMaxWidth() + PrimaryButton(config = config.primaryButtonConfig, modifier = buttonModifier) + + if (config.secondaryButtonConfig != null) { + SecondaryButton(config = config.secondaryButtonConfig, modifier = buttonModifier) + } + } + } +} + +@Composable +private fun PrimaryButton( + config: WalletBottomSheetConfig.BottomSheetContentConfig.ButtonConfig, + modifier: Modifier = Modifier, +) { + if (config.iconResId == null) { + PrimaryButton( + text = config.text, + onClick = config.onClick, + modifier = modifier, + ) + } else { + PrimaryButtonIconStart( + text = config.text, + iconResId = config.iconResId, + onClick = config.onClick, + modifier = modifier, + ) + } +} + +@Composable +private fun SecondaryButton( + config: WalletBottomSheetConfig.BottomSheetContentConfig.ButtonConfig, + modifier: Modifier = Modifier, +) { + if (config.iconResId == null) { + SecondaryButton( + text = config.text, + onClick = config.onClick, + modifier = modifier, + ) + } else { + SecondaryButtonIconStart( + text = config.text, + iconResId = config.iconResId, + onClick = config.onClick, + modifier = modifier, + ) + } +} + +@Preview +@Composable +private fun WalletBottomSheetContent_Light( + @PreviewParameter(WalletBottomSheetConfigProvider::class) + config: WalletBottomSheetConfig, +) { + TangemTheme(isDark = false) { + // Use preview of content because ModalBottomSheet isn't supported in Preview mode + BottomSheetContent(config = config.content) + } +} + +@Preview +@Composable +private fun WalletBottomSheetContent_Dark( + @PreviewParameter(WalletBottomSheetConfigProvider::class) + config: WalletBottomSheetConfig, +) { + TangemTheme(isDark = false) { + // Use preview of content because ModalBottomSheet isn't supported in Preview mode + BottomSheetContent(config = config.content) + } +} + +private class WalletBottomSheetConfigProvider : CollectionPreviewParameterProvider( + collection = listOf(WalletPreviewData.bottomSheet), +) \ No newline at end of file From 584d99e95e11518fa9ef509e36b9a339d0150472 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Jul 2023 14:18:24 +0300 Subject: [PATCH 56/58] Updated on 2026-08-14 --- .../tap/di/domain/TokensDomainModule.kt | 12 +++- .../coroutines/CoroutineDispatcherProvider.kt | 3 + .../domain/tokens/GetTokenListUseCase.kt | 5 +- .../tokens/ToggleTokenListSortingUseCase.kt | 66 +++++++++++++++++++ .../domain/tokens/error/TokenListError.kt | 19 ------ .../tokens/error/TokenListSortingError.kt | 10 +++ .../error/mapper/TokenListErrorMappers.kt | 25 +++++++ .../mapper/TokenListSortingErrorMappers.kt | 13 ++++ .../domain/tokens/model/NetworkGroup.kt | 3 +- .../tokens/operations/TokenListOperations.kt | 4 +- .../operations/TokenListSortingOperations.kt | 35 +++++++--- .../operations/TokenStatusOperations.kt | 2 +- .../operations/TokensStatusesOperations.kt | 4 +- .../domain/tokens/mock/MockTokenLists.kt | 11 ++-- .../utils/TokenListToContentItemsConverter.kt | 2 +- 15 files changed, 168 insertions(+), 46 deletions(-) create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListSortingError.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index bda1a7734d..a43ebfeb6c 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -1,6 +1,6 @@ package com.tangem.tap.di.domain -import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.* import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.tokens.repository.TokensRepository @@ -22,5 +22,13 @@ internal object TokensDomainModule { quotesRepository: QuotesRepository, networksRepository: NetworksRepository, dispatchers: CoroutineDispatcherProvider, - ) = GetTokenListUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers) + ): GetTokenListUseCase { + return GetTokenListUseCase(tokensRepository, quotesRepository, networksRepository, dispatchers) + } + + @Provides + @ViewModelScoped + fun provideToggleTokenListSortingUseCase(dispatchers: CoroutineDispatcherProvider): ToggleTokenListSortingUseCase { + return ToggleTokenListSortingUseCase(dispatchers) + } } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt index 6f28c0350e..f15bce01ea 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt @@ -9,18 +9,21 @@ import javax.inject.Inject interface CoroutineDispatcherProvider { val main: CoroutineDispatcher val io: CoroutineDispatcher + val default: CoroutineDispatcher val single: CoroutineDispatcher } class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider { override val main: CoroutineDispatcher = Dispatchers.Main override val io: CoroutineDispatcher = Dispatchers.IO + override val default: CoroutineDispatcher = Dispatchers.Default override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() } class TestingCoroutineDispatcherProvider( override val main: CoroutineDispatcher = Dispatchers.Unconfined, override val io: CoroutineDispatcher = Dispatchers.Unconfined, + override val default: CoroutineDispatcher = Dispatchers.Unconfined, override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(), ) : CoroutineDispatcherProvider diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index 3f42be91fc..fcd8578f39 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -6,6 +6,7 @@ import arrow.core.raise.Raise import arrow.core.raise.recover import arrow.core.right import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TokenStatus import com.tangem.domain.tokens.operations.TokenListOperations @@ -55,7 +56,7 @@ class GetTokenListUseCase( refresh = refresh, useCase = this@GetTokenListUseCase, raise = this, - transformError = TokenListError::fromTokenStatusesOperations, + transformError = TokensStatusesOperations.Error::mapToTokenListError, ) return operations.getTokensStatusesFlow() @@ -70,7 +71,7 @@ class GetTokenListUseCase( tokens = tokens, useCase = this@GetTokenListUseCase, raise = this, - transform = TokenListError::fromTokenListOperations, + transform = TokenListOperations.Error::mapToTokenListError, ) return operations.getTokenListFlow() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt new file mode 100644 index 0000000000..4b0b8664f4 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt @@ -0,0 +1,66 @@ +package com.tangem.domain.tokens + +import arrow.core.Either +import arrow.core.raise.Raise +import arrow.core.raise.either +import arrow.core.raise.ensure +import com.tangem.domain.tokens.error.TokenListSortingError +import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.operations.TokenListSortingOperations +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +class ToggleTokenListSortingUseCase( + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke(tokenList: TokenList): Either { + return withContext(dispatchers.default) { + either { + ensure(tokenList.totalFiatBalance !is TokenList.FiatBalance.Loading) { + TokenListSortingError.TokenListIsLoading + } + + when (tokenList) { + is TokenList.GroupedByNetwork -> sortGroupedTokenList(tokenList) + is TokenList.Ungrouped -> sortUngroupedTokenList(tokenList) + is TokenList.NotInitialized -> raise(TokenListSortingError.TokenListIsEmpty) + } + } + } + } + + private suspend fun Raise.sortGroupedTokenList( + tokenList: TokenList.GroupedByNetwork, + ): TokenList.GroupedByNetwork { + val operations = getSortingOperations(tokenList) + val networks = tokenList.groups.map { it.network }.toSet() + + return tokenList.copy( + groups = operations.getGroupedTokens(networks), + sortedBy = operations.getSortType(), + ) + } + + private suspend fun Raise.sortUngroupedTokenList( + tokenList: TokenList.Ungrouped, + ): TokenList.Ungrouped { + val operations = getSortingOperations(tokenList) + + return tokenList.copy( + tokens = operations.getTokens(), + sortedBy = operations.getSortType(), + ) + } + + private fun Raise.getSortingOperations(tokenList: TokenList): TokenListSortingOperations<*> { + return TokenListSortingOperations( + tokenList = tokenList, + sortByBalance = tokenList.sortedBy != TokenList.SortType.BALANCE, + dispatchers = dispatchers, + raise = this, + transformError = TokenListSortingOperations.Error::mapToTokenListSortingError, + ) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt index 06897839f6..0a321a7874 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListError.kt @@ -1,8 +1,6 @@ package com.tangem.domain.tokens.error import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.tokens.operations.TokenListOperations -import com.tangem.domain.tokens.operations.TokensStatusesOperations sealed class TokenListError { @@ -11,21 +9,4 @@ sealed class TokenListError { data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : TokenListError() data class DataError(val cause: Throwable) : TokenListError() - - internal companion object { - - fun fromTokenStatusesOperations(error: TokensStatusesOperations.Error): TokenListError = when (error) { - is TokensStatusesOperations.Error.DataError -> DataError(error.cause) - is TokensStatusesOperations.Error.EmptyNetworksStatuses, - is TokensStatusesOperations.Error.EmptyQuotes, - is TokensStatusesOperations.Error.EmptyTokens, - -> EmptyTokens - } - - fun fromTokenListOperations(error: TokenListOperations.Error): TokenListError = when (error) { - is TokenListOperations.Error.DataError -> DataError(error.cause) - is TokenListOperations.Error.UnableToSortTokenList -> UnableToSortTokenList(error.unsortedTokenList) - is TokenListOperations.Error.UnableToGroupTokenList -> UnableToSortTokenList(error.ungroupedTokenList) - } - } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListSortingError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListSortingError.kt new file mode 100644 index 0000000000..ae49dfb209 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/TokenListSortingError.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.tokens.error + +sealed class TokenListSortingError { + + object TokenListIsLoading : TokenListSortingError() + + object TokenListIsEmpty : TokenListSortingError() + + object UnableToSortTokenList : TokenListSortingError() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt new file mode 100644 index 0000000000..2d0798a489 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListErrorMappers.kt @@ -0,0 +1,25 @@ +package com.tangem.domain.tokens.error.mapper + +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.operations.TokenListOperations +import com.tangem.domain.tokens.operations.TokensStatusesOperations + +internal fun TokensStatusesOperations.Error.mapToTokenListError(): TokenListError { + return when (this) { + is TokensStatusesOperations.Error.DataError -> TokenListError.DataError(this.cause) + is TokensStatusesOperations.Error.EmptyNetworksStatuses, + is TokensStatusesOperations.Error.EmptyQuotes, + is TokensStatusesOperations.Error.EmptyTokens, + -> TokenListError.EmptyTokens + } +} + +internal fun TokenListOperations.Error.mapToTokenListError(): TokenListError { + return when (this) { + is TokenListOperations.Error.DataError -> TokenListError.DataError(this.cause) + is TokenListOperations.Error.UnableToSortTokenList -> + TokenListError.UnableToSortTokenList(this.unsortedTokenList) + is TokenListOperations.Error.UnableToGroupTokenList -> + TokenListError.UnableToSortTokenList(this.ungroupedTokenList) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt new file mode 100644 index 0000000000..2b492ad7ee --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.tokens.error.mapper + +import com.tangem.domain.tokens.error.TokenListSortingError +import com.tangem.domain.tokens.operations.TokenListSortingOperations + +internal fun TokenListSortingOperations.Error.mapToTokenListSortingError(): TokenListSortingError { + return when (this) { + is TokenListSortingOperations.Error.EmptyTokens -> TokenListSortingError.TokenListIsEmpty + is TokenListSortingOperations.Error.EmptyNetworks, + is TokenListSortingOperations.Error.NetworkNotFound, + -> TokenListSortingError.UnableToSortTokenList + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt index acc05ff374..d1cc917d38 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/model/NetworkGroup.kt @@ -1,7 +1,6 @@ package com.tangem.domain.tokens.model data class NetworkGroup( - val networkId: Network.ID, - val name: String, + val network: Network, val tokens: Set, ) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt index 87253155db..9283eacb78 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -51,7 +51,7 @@ internal class TokenListOperations( } private suspend fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList { - return withContext(dispatchers.single) { + return withContext(dispatchers.default) { val tokensNes = tokens.toNonEmptySetOrNull() ?: return@withContext TokenList.NotInitialized @@ -78,7 +78,7 @@ internal class TokenListOperations( val sortingOperations = TokenListSortingOperations( tokens = tokens, isAnyTokenLoading = isAnyTokenLoading, - isSortedByBalance = isSortedByBalance, + sortByBalance = isSortedByBalance, dispatchers = dispatchers, raise = this, transformError = { e -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt index 5d1b4516bb..e40520ec97 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt @@ -17,20 +17,40 @@ import java.math.BigDecimal internal class TokenListSortingOperations( private val tokens: Set, private val isAnyTokenLoading: Boolean, - private val isSortedByBalance: Boolean, + private val sortByBalance: Boolean, private val dispatchers: CoroutineDispatcherProvider, raise: Raise, transformError: (Error) -> E, ) : DelegatedRaise(raise, transformError) { + constructor( + tokenList: TokenList, + dispatchers: CoroutineDispatcherProvider, + raise: Raise, + transformError: (Error) -> E, + sortByBalance: Boolean = tokenList.sortedBy == TokenList.SortType.BALANCE, + isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TokenList.FiatBalance.Loading, + ) : this( + tokens = when (tokenList) { + is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.tokens }.toSet() + is TokenList.Ungrouped -> tokenList.tokens + is TokenList.NotInitialized -> emptySet() + }, + isAnyTokenLoading = isAnyTokenLoading, + sortByBalance = sortByBalance, + dispatchers = dispatchers, + raise = raise, + transformError = transformError, + ) + suspend fun getGroupedTokens(networks: Set): NonEmptySet { - return withContext(dispatchers.single) { + return withContext(dispatchers.default) { ensure(tokens.isNotEmpty()) { Error.EmptyTokens } val networksNes = ensureNotNull(networks.toNonEmptySetOrNull()) { Error.EmptyNetworks } - if (isSortedByBalance) { + if (sortByBalance) { groupAndSortTokensByBalance(networksNes) } else { groupTokens(networksNes) @@ -39,16 +59,16 @@ internal class TokenListSortingOperations( } suspend fun getTokens(): NonEmptySet { - return withContext(dispatchers.single) { + return withContext(dispatchers.default) { val tokensNes = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens } - if (isSortedByBalance) sortTokensByBalance(tokensNes) else tokensNes + if (sortByBalance) sortTokensByBalance(tokensNes) else tokensNes } } - fun getSortType() = if (isSortedByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE + fun getSortType() = if (sortByBalance) TokenList.SortType.BALANCE else TokenList.SortType.NONE private fun groupTokens(networks: NonEmptySet): NonEmptySet { val groupedTokens = tokens @@ -59,8 +79,7 @@ internal class TokenListSortingOperations( } NetworkGroup( - networkId = network.id, - name = network.name, + network = network, tokens = ensureNotNull(tokens.toNonEmptySetOrNull()) { Error.EmptyTokens }, ) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt index 072b541737..04478c7917 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenStatusOperations.kt @@ -15,7 +15,7 @@ internal class TokenStatusOperations( private val dispatchers: CoroutineDispatcherProvider, ) { - suspend fun createTokenStatus(): TokenStatus = withContext(dispatchers.single) { + suspend fun createTokenStatus(): TokenStatus = withContext(dispatchers.default) { TokenStatus( id = token.id, networkId = token.networkId, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt index e5dfd69fa0..cbe9d303ad 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokensStatusesOperations.kt @@ -64,7 +64,7 @@ internal class TokensStatusesOperations( tokens: Set, quotes: Set, networkStatuses: Set, - ): Set = withContext(dispatchers.single) { + ): Set = withContext(dispatchers.default) { tokens.mapTo(hashSetOf()) { token -> val quote = quotes.firstOrNull { it.tokenId == token.id } val networkStatus = networkStatuses.firstOrNull { it.networkId == token.networkId } @@ -106,7 +106,7 @@ internal class TokensStatusesOperations( } private suspend fun groupTokens(tokens: NonEmptySet): Map> = - withContext(dispatchers.single) { + withContext(dispatchers.default) { tokens .groupBy { it.networkId } .mapValues { (_, tokens) -> diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt index c705c614f2..b2befbeeb1 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokenLists.kt @@ -14,24 +14,21 @@ internal object MockTokenLists { const val isSortedByBalance = false val networkGroup1 = NetworkGroup( - networkId = MockNetworks.network1.id, - name = MockNetworks.network1.name, + network = MockNetworks.network1, tokens = MockTokensStates.tokenStates .filter { it.networkId == MockNetworks.network1.id } .toNonEmptySetOrNull()!!, ) val networkGroup2 = NetworkGroup( - networkId = MockNetworks.network2.id, - name = MockNetworks.network2.name, + network = MockNetworks.network2, tokens = MockTokensStates.tokenStates .filter { it.networkId == MockNetworks.network2.id } .toNonEmptySetOrNull()!!, ) val networkGroup3 = NetworkGroup( - networkId = MockNetworks.network3.id, - name = MockNetworks.network3.name, + network = MockNetworks.network3, tokens = MockTokensStates.tokenStates .filter { it.networkId == MockNetworks.network3.id } .toNonEmptySetOrNull()!!, @@ -42,7 +39,7 @@ internal object MockTokenLists { val sortedNetworksGroups = networksGroups.map { group -> group.copy( tokens = MockTokensStates.loadedTokensStates - .filter { it.networkId == group.networkId } + .filter { it.networkId == group.network.id } .sortedByDescending { it.value.fiatAmount } .toNonEmptySetOrNull()!!, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt index 731c766675..e488aa7b7f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToContentItemsConverter.kt @@ -44,7 +44,7 @@ internal class TokenListToContentItemsConverter( } private fun MutableList.addGroup(group: NetworkGroup): List { - this.add(MultiCurrencyItem.NetworkGroupTitle(group.name)) + this.add(MultiCurrencyItem.NetworkGroupTitle(group.network.name)) group.tokens.forEach { token -> this.addToken(token) From a57d077863883addf03228cbbd8c9fc45535c1da Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Jul 2023 20:57:39 +0800 Subject: [PATCH 57/58] Updated on 2026-08-14 --- .../presentation/common/WalletPreviewData.kt | 8 +- .../router/DefaultWalletRouter.kt | 6 +- .../wallet/state/WalletCardState.kt | 11 +- .../wallet/state/WalletStateHolder.kt | 107 ++++++++++++++- .../state/builder/WalletStateFactory.kt | 109 +++++++++++++++ .../wallet/ui/components/WalletsList.kt | 6 +- .../WalletContentItemDecoration.kt | 16 ++- .../utils/TokenListToWalletStateConverter.kt | 3 + .../wallet/viewmodels/WalletViewModel.kt | 128 ++++++++++-------- 9 files changed, 313 insertions(+), 81 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/builder/WalletStateFactory.kt diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt index 3e1d581e0c..5dfb53248c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -20,7 +20,7 @@ internal object WalletPreviewData { val walletTopBarConfig = WalletTopBarConfig(onScanCardClick = {}, onMoreClick = {}) val walletCardContentState = WalletCardState.Content( - id = UUID.randomUUID().toString(), + id = UserWalletId(UUID.randomUUID().toString()), title = "Wallet 1", balance = "8923,05 $", additionalInfo = "3 cards • Seed enabled", @@ -29,7 +29,7 @@ internal object WalletPreviewData { ) val walletCardLoadingState = WalletCardState.Loading( - id = UUID.randomUUID().toString(), + id = UserWalletId(UUID.randomUUID().toString()), title = "Wallet 1", additionalInfo = "3 cards • Seed enabled", imageResId = R.drawable.ill_businessman_3d, @@ -37,7 +37,7 @@ internal object WalletPreviewData { ) val walletCardHiddenContentState = WalletCardState.HiddenContent( - id = UUID.randomUUID().toString(), + id = UserWalletId(UUID.randomUUID().toString()), title = "Wallet 1", additionalInfo = "3 cards • Seed enabled", imageResId = R.drawable.ill_businessman_3d, @@ -45,7 +45,7 @@ internal object WalletPreviewData { ) val walletCardErrorState = WalletCardState.Error( - id = UUID.randomUUID().toString(), + id = UserWalletId(UUID.randomUUID().toString()), title = "Wallet 1", additionalInfo = "3 cards • Seed enabled", imageResId = R.drawable.ill_businessman_3d, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 2406ada436..2f30c3d355 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.hilt.navigation.compose.hiltViewModel @@ -40,8 +41,9 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation startDestination = WalletScreens.WALLET.name, ) { composable(WalletScreens.WALLET.name) { - val viewModel = hiltViewModel() - .apply { router = this@DefaultWalletRouter } + val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter } + LocalLifecycleOwner.current.lifecycle.addObserver(observer = viewModel) + WalletScreen(state = viewModel.uiState) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt index a04b0e3f45..b1f88360b1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt @@ -2,13 +2,14 @@ package com.tangem.feature.wallet.presentation.wallet.state import androidx.annotation.DrawableRes import androidx.compose.runtime.Immutable +import com.tangem.domain.wallets.models.UserWalletId /** Wallet card state */ @Immutable internal sealed interface WalletCardState { /** Id */ - val id: String + val id: UserWalletId /** Title */ val title: String @@ -34,7 +35,7 @@ internal sealed interface WalletCardState { * @property balance wallet balance */ data class Content( - override val id: String, + override val id: UserWalletId, override val title: String, override val additionalInfo: String, override val imageResId: Int?, @@ -52,7 +53,7 @@ internal sealed interface WalletCardState { * @property onClick lambda be invoked when wallet card is clicked */ data class Loading( - override val id: String, + override val id: UserWalletId, override val title: String, override val additionalInfo: String, override val imageResId: Int?, @@ -69,7 +70,7 @@ internal sealed interface WalletCardState { * @property onClick lambda be invoked when wallet card is clicked */ data class HiddenContent( - override val id: String, + override val id: UserWalletId, override val title: String, override val additionalInfo: String, override val imageResId: Int?, @@ -86,7 +87,7 @@ internal sealed interface WalletCardState { * @property onClick lambda be invoked when wallet card is clicked */ data class Error( - override val id: String, + override val id: UserWalletId, override val title: String, override val additionalInfo: String, override val imageResId: Int?, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index 66785bd0ee..169139dacd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -2,7 +2,9 @@ package com.tangem.feature.wallet.presentation.wallet.state import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.domain.wallets.models.UserWalletId import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf /** * Wallet screen state holder @@ -26,6 +28,44 @@ internal sealed class WalletStateHolder( open val bottomSheet: WalletBottomSheetConfig? = null, ) { + fun copySealed( + onBackClick: () -> Unit = this.onBackClick, + topBarConfig: WalletTopBarConfig = this.topBarConfig, + walletsListConfig: WalletsListConfig = this.walletsListConfig, + pullToRefreshConfig: WalletPullToRefreshConfig = this.pullToRefreshConfig, + contentItems: ImmutableList = this.contentItems, + notifications: ImmutableList = this.notifications, + bottomSheet: WalletBottomSheetConfig? = this.bottomSheet, + ): WalletStateHolder { + return when (this) { + is MultiCurrencyContent -> this.copy( + onBackClick = onBackClick, + topBarConfig = topBarConfig, + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + contentItems = contentItems as ImmutableList, + notifications = notifications, + bottomSheet = bottomSheet, + ) + is SingleCurrencyContent -> this.copy( + onBackClick = onBackClick, + topBarConfig = topBarConfig, + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + contentItems = contentItems as ImmutableList, + notifications = notifications, + bottomSheet = bottomSheet, + ) + is UnlockWalletContent -> this.copy( + onBackClick = onBackClick, + topBarConfig = topBarConfig, + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + ) + is Loading -> copy(onBackClick = onBackClick) + } + } + /** * Multi currency wallet content state * @@ -44,7 +84,7 @@ internal sealed class WalletStateHolder( override val pullToRefreshConfig: WalletPullToRefreshConfig, override val contentItems: ImmutableList, override val notifications: ImmutableList, - override val bottomSheet: WalletBottomSheetConfig, + override val bottomSheet: WalletBottomSheetConfig? = null, val onOrganizeTokensClick: () -> Unit, ) : WalletStateHolder( onBackClick = onBackClick, @@ -75,7 +115,7 @@ internal sealed class WalletStateHolder( override val pullToRefreshConfig: WalletPullToRefreshConfig, override val contentItems: ImmutableList, override val notifications: ImmutableList, - override val bottomSheet: WalletBottomSheetConfig, + override val bottomSheet: WalletBottomSheetConfig? = null, val buttons: ImmutableList, val marketPriceBlockState: MarketPriceBlockState, ) : WalletStateHolder( @@ -87,4 +127,67 @@ internal sealed class WalletStateHolder( notifications = notifications, bottomSheet = bottomSheet, ) + + /** + * Unlock wallet content state + * + * @property onBackClick lambda be invoked when back button is clicked + * @property topBarConfig top bar config + * @property walletsListConfig wallets list config + * @property onUnlockWalletsNotificationClick lambda be invoked when unlock wallets notification is clicked + * @property onBottomSheetDismissRequest lambda be invoked when bottom sheet is dismissed + * @property onUnlockClick lambda be invoked when unlock button is clicked + * @property onScanClick lambda be invoked when scan card button is clicked + */ + data class UnlockWalletContent( + override val onBackClick: () -> Unit, + override val topBarConfig: WalletTopBarConfig, + override val walletsListConfig: WalletsListConfig, + override val pullToRefreshConfig: WalletPullToRefreshConfig, + val onUnlockWalletsNotificationClick: () -> Unit, + val onBottomSheetDismissRequest: () -> Unit, + val onUnlockClick: () -> Unit, + val onScanClick: () -> Unit, + ) : WalletStateHolder( + onBackClick = onBackClick, + topBarConfig = topBarConfig, + walletsListConfig = walletsListConfig, + pullToRefreshConfig = pullToRefreshConfig, + contentItems = persistentListOf(WalletContentItemState.Loading), + notifications = persistentListOf(WalletNotification.UnlockWallets(onUnlockWalletsNotificationClick)), + bottomSheet = WalletBottomSheetConfig( + isShow = false, + onDismissRequest = onBottomSheetDismissRequest, + content = WalletBottomSheetConfig.BottomSheetContentConfig.UnlockWallets( + onUnlockClick = onUnlockClick, + onScanClick = onScanClick, + ), + ), + ) + + /** + * Loading state + * + * @property onBackClick lambda be invoked when back button is clicked + */ + data class Loading(override val onBackClick: () -> Unit) : WalletStateHolder( + onBackClick = onBackClick, + topBarConfig = WalletTopBarConfig(onScanCardClick = {}, onMoreClick = {}), + walletsListConfig = WalletsListConfig( + selectedWalletIndex = 0, + wallets = persistentListOf( + WalletCardState.Loading( + id = UserWalletId(stringValue = ""), + title = "", + additionalInfo = "", + imageResId = null, + ), + ), + onWalletChange = {}, + ), + pullToRefreshConfig = WalletPullToRefreshConfig(isRefreshing = false, onRefresh = {}), + contentItems = persistentListOf(WalletContentItemState.Loading), + notifications = persistentListOf(), + bottomSheet = null, + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/builder/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/builder/WalletStateFactory.kt new file mode 100644 index 0000000000..21bf52bee0 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/builder/WalletStateFactory.kt @@ -0,0 +1,109 @@ +package com.tangem.feature.wallet.presentation.wallet.state.builder + +import com.tangem.common.Provider +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.router.InnerWalletRouter +import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver +import com.tangem.feature.wallet.presentation.wallet.state.* +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +internal class WalletStateFactory( + private val routerProvider: Provider, + private val onScanCardClick: () -> Unit, + private val onWalletChange: (Int) -> Unit, + private val onRefreshSwipe: () -> Unit, +) { + + fun getInitialState(): WalletStateHolder = WalletStateHolder.Loading(onBackClick = ::onBackClick) + + fun getContentState(wallets: List): WalletStateHolder { + val cardTypeResolver = requireNotNull(wallets.firstOrNull()).scanResponse.cardTypesResolver + + return if (cardTypeResolver.isMultiwalletAllowed()) { + createMultiCurrencyState(wallets) + } else { + createSingleCurrencyState(wallets) + } + } + + private fun createMultiCurrencyState(wallets: List): WalletStateHolder.MultiCurrencyContent { + return WalletStateHolder.MultiCurrencyContent( + onBackClick = ::onBackClick, + topBarConfig = createTopBarConfig(), + walletsListConfig = createWalletsListConfig(wallets), + pullToRefreshConfig = createPullToRefreshConfig(), + contentItems = persistentListOf(), + notifications = persistentListOf(), // TODO: create notifications + bottomSheet = WalletBottomSheetConfig( + // TODO: check notifications + isShow = false, + onDismissRequest = {}, + content = WalletBottomSheetConfig.BottomSheetContentConfig.LikeTangemApp( + onRateTheAppClick = {}, + onShareClick = {}, + ), + ), + onOrganizeTokensClick = routerProvider()::openOrganizeTokensScreen, + ) + } + + private fun createSingleCurrencyState(wallets: List): WalletStateHolder.SingleCurrencyContent { + return WalletStateHolder.SingleCurrencyContent( + onBackClick = ::onBackClick, + topBarConfig = createTopBarConfig(), + walletsListConfig = createWalletsListConfig(wallets), + pullToRefreshConfig = createPullToRefreshConfig(), + contentItems = persistentListOf(), + notifications = persistentListOf(), // TODO: create notifications + bottomSheet = WalletBottomSheetConfig( + // TODO: check notifications + isShow = false, + onDismissRequest = {}, + content = WalletBottomSheetConfig.BottomSheetContentConfig.LikeTangemApp( + onRateTheAppClick = {}, + onShareClick = {}, + ), + ), + buttons = WalletPreviewData.singleWalletScreenState.buttons, // TODO: create buttons + // TODO: create market price block + marketPriceBlockState = WalletPreviewData.singleWalletScreenState.marketPriceBlockState, + ) + } + + private fun onBackClick() = routerProvider().popBackStack() + + private fun createTopBarConfig(): WalletTopBarConfig { + return WalletTopBarConfig( + onScanCardClick = onScanCardClick, + onMoreClick = routerProvider()::openDetailsScreen, + ) + } + + private fun createWalletsListConfig(wallets: List): WalletsListConfig { + return WalletsListConfig( + selectedWalletIndex = 0, + wallets = wallets.map { wallet -> + WalletCardState.Loading( + id = if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { // TODO + UserWalletId("123") + } else { + UserWalletId("321") + }, + // TODO: wallet.walletId, + title = wallet.name, + additionalInfo = "", // TODO + imageResId = WalletImageResolver.resolve(cardTypesResolver = wallet.scanResponse.cardTypesResolver), + ) + }.toImmutableList(), + onWalletChange = onWalletChange, + ) + } + + private fun createPullToRefreshConfig(): WalletPullToRefreshConfig { + return WalletPullToRefreshConfig(isRefreshing = false, onRefresh = onRefreshSwipe) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt index 6c39b86f10..3f162e41f0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletsList.kt @@ -19,10 +19,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.WalletsListConfig import com.tangem.feature.wallet.presentation.wallet.ui.utils.ScrollOffsetCollector -import kotlinx.coroutines.InternalCoroutinesApi /** * Wallets list component @@ -32,7 +30,7 @@ import kotlinx.coroutines.InternalCoroutinesApi * [REDACTED_AUTHOR] */ -@OptIn(ExperimentalFoundationApi::class, InternalCoroutinesApi::class) +@OptIn(ExperimentalFoundationApi::class) @Composable internal fun WalletsList(config: WalletsListConfig, lazyListState: LazyListState, modifier: Modifier = Modifier) { val horizontalCardPadding = TangemTheme.dimens.spacing16 @@ -45,7 +43,7 @@ internal fun WalletsList(config: WalletsListConfig, lazyListState: LazyListState horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState), ) { - items(items = config.wallets, key = WalletCardState::id) { state -> + items(items = config.wallets, key = { it.id.stringValue }) { state -> WalletCard(state = state, modifier = Modifier.width(itemWidth)) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/decorations/WalletContentItemDecoration.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/decorations/WalletContentItemDecoration.kt index ea04e144c4..b06ad40f02 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/decorations/WalletContentItemDecoration.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/decorations/WalletContentItemDecoration.kt @@ -12,21 +12,27 @@ import com.tangem.core.ui.res.TangemTheme */ internal fun Modifier.walletContentItemDecoration(currentIndex: Int, lastIndex: Int): Modifier = composed { val modifierWithHorizontalPadding = this.padding(horizontal = TangemTheme.dimens.spacing16) - when (currentIndex) { - 0 -> { + val isSingleItem = currentIndex == 0 && lastIndex == 0 + when { + isSingleItem -> { + modifierWithHorizontalPadding + .padding(top = TangemTheme.dimens.spacing14) + .clip(shape = TangemTheme.shapes.roundedCornersXMedium) + } + currentIndex == 0 -> { modifierWithHorizontalPadding .padding(top = TangemTheme.dimens.spacing14) .clip( - RoundedCornerShape( + shape = RoundedCornerShape( topStart = TangemTheme.dimens.radius16, topEnd = TangemTheme.dimens.radius16, ), ) } - lastIndex -> { + currentIndex == lastIndex -> { modifierWithHorizontalPadding .clip( - RoundedCornerShape( + shape = RoundedCornerShape( bottomStart = TangemTheme.dimens.radius16, bottomEnd = TangemTheme.dimens.radius16, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt index af7cf9373e..1db861142b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt @@ -19,6 +19,9 @@ internal class TokenListToWalletStateConverter( return when (currentState) { is MultiCurrencyContent -> currentState.updateWithTokenList(value) is SingleCurrencyContent -> currentState.updateWithTokenList(value) + is WalletStateHolder.Loading, + is WalletStateHolder.UnlockWalletContent, + -> currentState } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index c606c9e404..a369895e5b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -1,35 +1,33 @@ package com.tangem.feature.wallet.presentation.wallet.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 androidx.lifecycle.* import arrow.core.Either +import com.tangem.common.Provider import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.core.ui.components.transactions.TransactionState import com.tangem.domain.card.GetAccessCodeSavingStatusUseCase import com.tangem.domain.card.GetBiometricsStatusUseCase import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase import com.tangem.domain.tokens.GetTokenListUseCase -import com.tangem.domain.userwallets.UserWalletBuilder -import com.tangem.domain.wallets.usecase.SaveWalletUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.usecase.GetWalletsUseCase +import com.tangem.domain.wallets.usecase.SaveWalletUseCase +import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder -import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig -import com.tangem.feature.wallet.presentation.wallet.utils.LoadingItemsProvider.getLoadingMultiCurrencyTokens +import com.tangem.feature.wallet.presentation.wallet.state.* +import com.tangem.feature.wallet.presentation.wallet.state.builder.WalletStateFactory import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorToWalletStateConverter import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @@ -48,50 +46,66 @@ internal class WalletViewModel @Inject constructor( private val setAccessCodeRequestPolicyUseCase: SetAccessCodeRequestPolicyUseCase, private val getAccessCodeSavingStatusUseCase: GetAccessCodeSavingStatusUseCase, private val getTokenListUseCase: GetTokenListUseCase, + private val getWalletsUseCase: GetWalletsUseCase, private val scanCardProcessor: ScanCardProcessor, private val dispatchers: CoroutineDispatcherProvider, -) : ViewModel() { +) : ViewModel(), DefaultLifecycleObserver { /** Feature router */ var router: InnerWalletRouter by Delegates.notNull() + private val stateFactory = WalletStateFactory( + routerProvider = Provider { router }, + onScanCardClick = ::onScanCardClick, + onWalletChange = ::changeWallet, + onRefreshSwipe = ::refreshContent, + ) + /** Screen state */ - var uiState by mutableStateOf(getInitialState()) + var uiState by mutableStateOf(stateFactory.getInitialState()) private set - private var getTokenListJob: Job? = null - set(value) { - field?.cancel() - field = value + override fun onCreate(owner: LifecycleOwner) { + getWalletsUseCase() + .distinctUntilChanged() + .flowWithLifecycle(owner.lifecycle) + .onEach { wallets -> + if (wallets.isEmpty()) return@onEach + + uiState = stateFactory.getContentState(wallets = wallets) + updateContentItems() + } + .flowOn(dispatchers.io) + .launchIn(viewModelScope) + } + + @OptIn(ExperimentalCoroutinesApi::class) + private fun updateContentItems() { + getTokenListUseCase( + userWalletId = uiState.walletsListConfig.wallets.get( + index = uiState.walletsListConfig.selectedWalletIndex, + ).id, + ) + .distinctUntilChanged() + .mapLatest(::updateStateWithTokenListOrError) + .onEach { + uiState = it.copySealed( + pullToRefreshConfig = uiState.pullToRefreshConfig.copy(isRefreshing = getRefreshingStatus()), + ) + } + .flowOn(dispatchers.io) + .launchIn(viewModelScope) + } + + private fun getRefreshingStatus(): Boolean { + return uiState.contentItems.any { state -> + val isMultiCurrencyItem = state as? WalletContentItemState.MultiCurrencyItem.Token + val isSingleCurrencyItem = state as? WalletContentItemState.SingleCurrencyItem.Transaction + + isMultiCurrencyItem?.state is TokenItemState.Loading || + isSingleCurrencyItem?.state is TransactionState.Loading || + state is WalletContentItemState.Loading } - - // TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData - private fun getInitialState(): WalletStateHolder { - val state = WalletPreviewData.multicurrencyWalletScreenState.copy( - onBackClick = ::onBackClick, - topBarConfig = createTopBarConfig(), - walletsListConfig = WalletPreviewData.multicurrencyWalletScreenState.walletsListConfig.copy( - onWalletChange = ::selectWallet, - ), - contentItems = getLoadingMultiCurrencyTokens(), - ) - - val selectedWalletIndex = state.walletsListConfig.selectedWalletIndex - val selectedWalletId = WalletPreviewData.wallets.keys.elementAt(selectedWalletIndex) - launchGetTokenListJob(selectedWalletId) - - return state - } - - private fun onBackClick() { - router.popBackStack() - } - - private fun createTopBarConfig(): WalletTopBarConfig { - return WalletTopBarConfig( - onScanCardClick = ::onScanCardClick, - onMoreClick = { router.openDetailsScreen() }, - ) } private fun onScanCardClick() { @@ -124,12 +138,9 @@ internal class WalletViewModel @Inject constructor( } } - // TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData - private fun selectWallet(index: Int) { + private fun changeWallet(index: Int) { if (uiState.walletsListConfig.selectedWalletIndex == index) return - Log.i("WalletViewModel", "selectWallet: $index") - uiState = when (val state = uiState) { is WalletStateHolder.MultiCurrencyContent -> state.copy( walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), @@ -137,19 +148,18 @@ internal class WalletViewModel @Inject constructor( is WalletStateHolder.SingleCurrencyContent -> state.copy( walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), ) + is WalletStateHolder.UnlockWalletContent -> state.copy( + walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), + ) + is WalletStateHolder.Loading -> state } - val selectedWalletId = WalletPreviewData.wallets.keys.elementAt(index) - launchGetTokenListJob(selectedWalletId) + updateContentItems() } - private fun launchGetTokenListJob(userWalletId: UserWalletId) { - getTokenListJob = getTokenListUseCase(userWalletId) - .distinctUntilChanged() - .mapLatest(::updateStateWithTokenListOrError) - .onEach { uiState = it } - .flowOn(Dispatchers.Default) - .launchIn(viewModelScope) + private fun refreshContent() { + uiState = uiState.copySealed(pullToRefreshConfig = uiState.pullToRefreshConfig.copy(isRefreshing = true)) + updateContentItems() } private fun updateStateWithTokenListOrError(tokenList: Either): WalletStateHolder { From 11f475954468fe653303456678d641690d1f174f Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Jul 2023 20:00:23 +0300 Subject: [PATCH 58/58] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + .../datasource/di/CacheKeysStoreModule.kt | 20 +++++++ .../datasource/local/cache/CacheKeysStore.kt | 14 +++++ .../local/cache/RuntimeCacheKeysStore.kt | 25 ++++++++ .../datasource/local/cache/model/CacheKey.kt | 10 ++++ .../datasource/local/store/RuntimeStore.kt | 59 +++++++++++++++++++ data/common/.gitignore | 1 + data/common/build.gradle.kts | 16 +++++ data/common/src/main/AndroidManifest.xml | 1 + .../tangem/data/common/cache/CacheRegistry.kt | 52 ++++++++++++++++ .../data/common/cache/DefaultCacheRegistry.kt | 52 ++++++++++++++++ .../common/cache/di/CacheRegistryModule.kt | 21 +++++++ settings.gradle.kts | 1 + 13 files changed, 273 insertions(+) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/di/CacheKeysStoreModule.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/cache/CacheKeysStore.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/cache/RuntimeCacheKeysStore.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/cache/model/CacheKey.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/store/RuntimeStore.kt create mode 100644 data/common/.gitignore create mode 100644 data/common/build.gradle.kts create mode 100644 data/common/src/main/AndroidManifest.xml create mode 100644 data/common/src/main/kotlin/com/tangem/data/common/cache/CacheRegistry.kt create mode 100644 data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt create mode 100644 data/common/src/main/kotlin/com/tangem/data/common/cache/di/CacheRegistryModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 75db227f9d..8b1db8e62c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(project(":data:source:preferences")) implementation(projects.data.card) implementation(projects.data.tokens) + implementation(projects.data.common) /** Features */ implementation(project(":features:onboarding")) diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/CacheKeysStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/CacheKeysStoreModule.kt new file mode 100644 index 0000000000..ae96179015 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/di/CacheKeysStoreModule.kt @@ -0,0 +1,20 @@ +package com.tangem.datasource.di + +import com.tangem.datasource.local.cache.CacheKeysStore +import com.tangem.datasource.local.cache.RuntimeCacheKeysStore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object CacheKeysStoreModule { + + @Provides + @Singleton + fun provideCacheKeysStore(): CacheKeysStore { + return RuntimeCacheKeysStore() + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/cache/CacheKeysStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/cache/CacheKeysStore.kt new file mode 100644 index 0000000000..fc32a2b16e --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/cache/CacheKeysStore.kt @@ -0,0 +1,14 @@ +package com.tangem.datasource.local.cache + +import com.tangem.datasource.local.cache.model.CacheKey + +interface CacheKeysStore { + + fun get(id: String): CacheKey? + + fun addOrReplace(key: CacheKey) + + fun remove(id: String) + + fun clear() +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/cache/RuntimeCacheKeysStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/cache/RuntimeCacheKeysStore.kt new file mode 100644 index 0000000000..229b5ab30c --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/cache/RuntimeCacheKeysStore.kt @@ -0,0 +1,25 @@ +package com.tangem.datasource.local.cache + +import com.tangem.datasource.local.cache.model.CacheKey +import com.tangem.datasource.local.store.RuntimeStore + +internal class RuntimeCacheKeysStore : CacheKeysStore { + + private val store = RuntimeStore(keyProvider = CacheKey::id) + + override fun get(id: String): CacheKey? { + return store.getSync { it.id == id }.firstOrNull() + } + + override fun addOrReplace(key: CacheKey) { + store.addOrReplace(key) + } + + override fun remove(id: String) { + store.remove { it.id == id } + } + + override fun clear() { + store.clear() + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/cache/model/CacheKey.kt b/core/datasource/src/main/java/com/tangem/datasource/local/cache/model/CacheKey.kt new file mode 100644 index 0000000000..733e61e2b0 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/cache/model/CacheKey.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.local.cache.model + +import org.joda.time.Duration +import org.joda.time.LocalDateTime + +data class CacheKey( + val id: String, + val updatedAt: LocalDateTime, + val expiresIn: Duration, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/store/RuntimeStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/store/RuntimeStore.kt new file mode 100644 index 0000000000..37a05f8d3e --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/store/RuntimeStore.kt @@ -0,0 +1,59 @@ +package com.tangem.datasource.local.store + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update + +internal class RuntimeStore(private val keyProvider: (Data) -> Key) { + + private val store = MutableStateFlow>(hashMapOf()) + + fun get(selector: (Data) -> Boolean = { true }): Flow> { + return store.map { getInternal(it, selector) } + } + + fun getSync(selector: (Data) -> Boolean = { true }): List { + return getInternal(store.value, selector) + } + + // TODO: Uncomment if needed + // fun addOrReplace(items: Collection) { + // if (items.isEmpty()) return + // + // val storeValue = store.value + // + // items.forEach { item -> + // storeValue[keyProvider(item)] = item + // } + // + // store.value = storeValue + // } + + fun addOrReplace(item: Data) { + val storeValue = store.value + storeValue[keyProvider(item)] = item + + store.value = storeValue + } + + fun remove(selector: (Data) -> Boolean) { + val storeValue = store.value + + storeValue.forEach { (key, item) -> + if (selector(item)) { + storeValue.remove(key) + } + } + + store.value = storeValue + } + + fun clear() { + store.update { hashMapOf() } + } + + private fun getInternal(store: HashMap, selector: (Data) -> Boolean): List { + return store.values.filter { selector(it) } + } +} \ No newline at end of file diff --git a/data/common/.gitignore b/data/common/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/data/common/.gitignore @@ -0,0 +1 @@ +/build diff --git a/data/common/build.gradle.kts b/data/common/build.gradle.kts new file mode 100644 index 0000000000..7ddd807653 --- /dev/null +++ b/data/common/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + id("configuration") +} + +dependencies { + implementation(projects.core.datasource) + + implementation(deps.kotlin.coroutines) + implementation(deps.jodatime) + + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/data/common/src/main/AndroidManifest.xml b/data/common/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..5a762109a1 --- /dev/null +++ b/data/common/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/CacheRegistry.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/CacheRegistry.kt new file mode 100644 index 0000000000..fb698486ad --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/CacheRegistry.kt @@ -0,0 +1,52 @@ +package com.tangem.data.common.cache + +import org.joda.time.Duration + +/** + * Represents a registry for managing cache. + */ +interface CacheRegistry { + + /** + * Checks whether the cache key is expired. + * + * @param key cache key. + * @return `true` if the cache key is expired, `false` otherwise. + */ + suspend fun isExpired(key: String): Boolean + + /** + * Invalidates the cache key in registry. + * + * If the key doesn't exist, or it's already invalidated, this method doesn't have any effect. + * + * @param key cache key. + */ + suspend fun invalidate(key: String) + + /** + * Invalidates all cache keys in the registry. + * + * After the call, the registry doesn't contain any valid keys. + */ + suspend fun invalidateAll() + + /** + * Defines a callback to be invoked when the cache key expires. + * + * @param key cache key. + * @param skipCache if `true`, the callback will be invoked regardless of whether the key has expired or not. + * @param expireIn the duration after which the cache key is considered expired. + * @param block the block of code to be executed when the cache key expires. + */ + suspend fun invokeOnExpire( + key: String, + skipCache: Boolean, + expireIn: Duration = Duration.standardMinutes(DEFAULT_CACHE_KEY_EXPIRE_IN_MINUTES), + block: suspend () -> Unit, + ) + + private companion object { + const val DEFAULT_CACHE_KEY_EXPIRE_IN_MINUTES = 5L + } +} \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt new file mode 100644 index 0000000000..1aabdd2e77 --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/DefaultCacheRegistry.kt @@ -0,0 +1,52 @@ +package com.tangem.data.common.cache + +import com.tangem.datasource.local.cache.CacheKeysStore +import com.tangem.datasource.local.cache.model.CacheKey +import org.joda.time.Duration +import org.joda.time.LocalDateTime + +internal class DefaultCacheRegistry( + private val cacheKeysStore: CacheKeysStore, +) : CacheRegistry { + + override suspend fun isExpired(key: String): Boolean { + val cacheKey = cacheKeysStore.get(key) ?: return true + + return cacheKey.updatedAt + .plus(cacheKey.expiresIn) + .isBefore(LocalDateTime.now()) + } + + override suspend fun invalidate(key: String) { + cacheKeysStore.remove(key) + } + + override suspend fun invalidateAll() { + cacheKeysStore.clear() + } + + override suspend fun invokeOnExpire( + key: String, + skipCache: Boolean, + expireIn: Duration, + block: suspend () -> Unit, + ) { + val isExpired = isExpired(key) || skipCache + if (!isExpired) return + + cacheKeysStore.addOrReplace( + key = CacheKey( + id = key, + updatedAt = LocalDateTime.now(), + expiresIn = expireIn, + ), + ) + + try { + block() + } catch (e: Throwable) { + invalidate(key) + throw e + } + } +} \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/cache/di/CacheRegistryModule.kt b/data/common/src/main/kotlin/com/tangem/data/common/cache/di/CacheRegistryModule.kt new file mode 100644 index 0000000000..84359cb2e6 --- /dev/null +++ b/data/common/src/main/kotlin/com/tangem/data/common/cache/di/CacheRegistryModule.kt @@ -0,0 +1,21 @@ +package com.tangem.data.common.cache.di + +import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.common.cache.DefaultCacheRegistry +import com.tangem.datasource.local.cache.CacheKeysStore +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object CacheRegistryModule { + + @Provides + @Singleton + fun provideCacheRegistry(cacheKeysStore: CacheKeysStore): CacheRegistry { + return DefaultCacheRegistry(cacheKeysStore) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 4cd9f9833e..276169d26e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -85,6 +85,7 @@ include(":domain:tokens") // endregion Domain modules // region Data modules +include(":data:common") include(":data:card") include(":data:tokens") include(":data:source:preferences")