diff --git a/.gitignore b/.gitignore
index 25c98d5d1f..3e96fb9767 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
# Built application files
/build
+/buildSrc
# Local configuration file (sdk path, etc)
local.properties
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 7fc608245b..0db8c6d7c9 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -13,9 +13,6 @@
-
-
-
@@ -205,4 +202,4 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a9226c6eff..ef93ea2179 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -27,16 +27,16 @@
@@ -45,10 +45,11 @@
android:value="true" />
@@ -125,13 +126,17 @@
-
+
-
+
{
preparedData = state.dialog.data,
context = context,
)
- is WalletConnectDialog.UnsupportedNetwork ->
+ is WalletConnectDialog.UnsupportedNetwork -> {
+ val warning = if (state.dialog.networks.isNullOrEmpty()) {
+ context.getString(R.string.wallet_connect_scanner_error_unsupported_network)
+ } else {
+ context.getString(R.string.wallet_connect_error_unsupported_blockchains) +
+ state.dialog.networks
+ }
SimpleAlertDialog.create(
titleRes = R.string.wallet_connect_title,
- messageRes = R.string.wallet_connect_scanner_error_unsupported_network,
+ message = warning,
context = context,
)
+ }
is WalletConnectDialog.SessionProposalDialog -> {
SessionProposalDialog.create(
sessionProposal = state.dialog.sessionProposal,
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
index 5b0135f4a7..7ca3e575d4 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
@@ -10,7 +10,6 @@ import com.tangem.wallet.R
fun Blockchain.getGreyedOutIconRes(): Int {
return when (this) {
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> R.drawable.ic_arbitrum_no_color
-// Blockchain.Ducatus -> R.drawable.ic_ducatus
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> R.drawable.ic_bitcoin_no_color
Blockchain.BitcoinCash -> R.drawable.ic_bitcoin_cash_no_color
Blockchain.Litecoin -> R.drawable.ic_litecoin_no_color
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
index c9f2314efc..91c1ae14b7 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt
@@ -6,7 +6,6 @@ import com.tangem.tap.common.analytics.topup.TopUpController
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.redux.StateDialog
-import com.tangem.tap.domain.PayIdManager
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
@@ -15,11 +14,11 @@ import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import org.rekotlin.StateType
data class GlobalState(
+ @Deprecated("Use scan response from selected user wallet")
val scanResponse: ScanResponse? = null,
val onboardingState: OnboardingState = OnboardingState(),
val cardVerifiedOnline: Boolean = false,
val tapWalletManager: TapWalletManager = TapWalletManager(),
- val payIdManager: PayIdManager = PayIdManager(),
val configManager: ConfigManager? = null,
val warningManager: WarningMessagesManager? = null,
val feedbackManager: FeedbackManager? = null,
diff --git a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt b/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
deleted file mode 100644
index 16565ff5a6..0000000000
--- a/app/src/main/java/com/tangem/tap/domain/PayIdManager.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.tangem.tap.domain
-
-import com.tangem.blockchain.common.Blockchain
-import com.tangem.common.services.Result
-import com.tangem.tap.network.payid.PayIdVerifyService
-import com.tangem.tap.network.payid.VerifyPayIdResponse
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import java.util.*
-
-class PayIdManager {
-
- @Suppress("MagicNumber")
- suspend fun verifyPayId(payId: String, blockchain: Blockchain): Result =
- withContext(Dispatchers.IO) {
- val splitPayId = payId.split("\$")
- val user = splitPayId[0]
- val baseUrl = "https://${splitPayId[1]}/"
- return@withContext PayIdVerifyService(baseUrl).verifyAddress(user, blockchain.getPayIdNetwork())
- }
-
- private fun Blockchain.getPayIdNetwork(): String {
- return when (this) {
- Blockchain.XRP -> "XRPL"
- Blockchain.RSK -> "RSK"
- else -> this.currency
- }.lowercase(Locale.getDefault())
- }
-
- companion object {
- private val payIdRegExp = (
- "^[a-z0-9!#@%&*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#@%&*+/=?^_`{|}~-]+)*\\\$(?:(?:[a-z0-9]" +
- "(?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z-]*[a-z0-9])?|(?:[0-9]{1,3}\\.){3}[0-9]{1,3})\$"
- ).toRegex()
-
- val payIdSupported: EnumSet = EnumSet.of(
- Blockchain.XRP,
- Blockchain.Ethereum,
- Blockchain.Bitcoin,
- Blockchain.Litecoin,
- Blockchain.Stellar,
- Blockchain.Cardano,
- Blockchain.CardanoShelley,
- Blockchain.Ducatus,
- Blockchain.BitcoinCash,
- Blockchain.Binance,
- Blockchain.RSK,
- Blockchain.Tezos,
- )
-
- fun isPayId(value: String?): Boolean = value?.contains(payIdRegExp) ?: false
- }
-}
-
-fun Blockchain.isPayIdSupported(): Boolean {
- return PayIdManager.payIdSupported.contains(this)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
index 50182bde55..4e39b73bbe 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -1,6 +1,9 @@
package com.tangem.tap.domain
-import com.tangem.blockchain.common.*
+import com.tangem.blockchain.common.BlockchainSdkConfig
+import com.tangem.blockchain.common.Token
+import com.tangem.blockchain.common.Wallet
+import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.core.analytics.Analytics
@@ -83,9 +86,9 @@ class TapWalletManager(
}
private fun setupWalletConnectV2(userWallet: UserWallet) {
- val cardId = if (userWallet.cardsInWallet.size == 1) {
+ val cardId = if (userWallet.scanResponse.card.backupStatus?.isActive != true) {
userWallet.cardId
- } else {
+ } else { // if wallet has backup, any card from wallet can be used to sign
null
}
scope.launch {
@@ -134,22 +137,13 @@ class TapWalletManager(
fun updateConfigManager(data: ScanResponse) {
val configManager = store.state.globalState.configManager
- val blockchain = data.cardTypesResolver.getBlockchain()
+
if (data.cardTypesResolver.isStart2Coin()) {
- configManager?.turnOff(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
configManager?.turnOff(ConfigManager.IS_TOP_UP_ENABLED)
- } else if (blockchain == Blockchain.Bitcoin ||
- data.walletData?.blockchain == Blockchain.Bitcoin.id
- ) {
- configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
- configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED)
} else {
- configManager?.resetToDefault(ConfigManager.IS_SENDING_TO_PAY_ID_ENABLED)
configManager?.resetToDefault(ConfigManager.IS_TOP_UP_ENABLED)
}
}
}
-fun Wallet.getFirstToken(): Token? {
- return getTokens().toList().getOrNull(0)
-}
\ No newline at end of file
+fun Wallet.getFirstToken(): Token? = getTokens().toList().getOrNull(index = 0)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
index 78f74e0c3d..4ea180e270 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
@@ -118,7 +118,9 @@ internal class BiometricUserWalletsListManager(
}
.flatMap { updatedUserWallet ->
saveInternal(updatedUserWallet, changeSelectedUserWallet = false)
- .map { updatedUserWallet }
+ }
+ .flatMap {
+ get(userWalletId)
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt
index f344f46d4a..0bbd76b7bc 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletCurrencies/implementation/DefaultWalletCurrenciesManager.kt
@@ -1,13 +1,10 @@
package com.tangem.tap.domain.walletCurrencies.implementation
import com.tangem.blockchain.common.DerivationStyle
-import com.tangem.common.CompletionResult
-import com.tangem.common.flatMap
-import com.tangem.common.fold
-import com.tangem.common.map
-import com.tangem.domain.models.scan.CardDTO
+import com.tangem.common.*
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.util.UserWalletId
+import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.domain.model.builders.WalletStoreBuilder
@@ -103,7 +100,7 @@ internal class DefaultWalletCurrenciesManager(
.flatMap {
updateWalletStores(userWallet, remainingCurrencies.toBlockchainNetworks())
}
- .map {
+ .doOnResult {
saveUserCurrencies(card, remainingCurrencies)
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt
index e276bd51db..22dd64bff1 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletStores/repository/implementation/DefaultWalletAmountsRepository.kt
@@ -7,16 +7,11 @@ import com.tangem.blockchain.common.Wallet
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.extensions.Result.Failure
import com.tangem.blockchain.extensions.Result.Success
-import com.tangem.common.CompletionResult
-import com.tangem.common.catching
+import com.tangem.common.*
import com.tangem.common.core.TangemError
-import com.tangem.common.flatMap
-import com.tangem.common.flatMapOnFailure
-import com.tangem.common.fold
-import com.tangem.common.map
import com.tangem.datasource.api.tangemTech.TangemTechApi
-import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.common.util.UserWalletId
+import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.TestActions
import com.tangem.tap.common.entities.FiatCurrency
@@ -25,15 +20,7 @@ import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.domain.model.WalletStoreModel
import com.tangem.tap.domain.walletStores.WalletStoresError
import com.tangem.tap.domain.walletStores.repository.WalletAmountsRepository
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStore
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.replaceWalletStores
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithAmounts
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithDemoAmounts
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithError
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithFiatRates
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithMissedDerivation
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithRent
-import com.tangem.tap.domain.walletStores.repository.implementation.utils.updateWithUnreachable
+import com.tangem.tap.domain.walletStores.repository.implementation.utils.*
import com.tangem.tap.domain.walletStores.storage.WalletManagerStorage
import com.tangem.tap.domain.walletStores.storage.WalletStoresStorage
import com.tangem.tap.features.demo.DemoHelper
@@ -45,13 +32,8 @@ import com.tangem.tap.features.wallet.models.getPendingTransactions
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.async
-import kotlinx.coroutines.awaitAll
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.delay
+import kotlinx.coroutines.*
import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.withContext
import timber.log.Timber
import java.math.BigDecimal
import kotlin.time.Duration
@@ -200,37 +182,40 @@ internal class DefaultWalletAmountsRepository(
walletStore: WalletStoreModel,
walletManager: WalletManager?,
): CompletionResult {
- val hasMissedDerivations = with(walletStore) {
+ val isDerivationMissed = with(walletStore) {
derivationPath != null && !scanResponse.hasDerivation(blockchain, derivationPath.rawPath)
}
return when {
- hasMissedDerivations -> {
+ isDerivationMissed -> {
updateWalletStoreWithMissedDerivation(walletStore)
}
walletManager == null -> {
updateWalletStoreWithUnreachable(walletStore)
}
else -> {
- updateWalletManager(scanResponse, walletManager).map {
- updateWalletManagerInStorage(
- userWalletId,
- walletManager,
- )
- }.flatMap {
- updateWalletStoreWithAmounts(
- walletStore = walletStore,
- updatedWallet = walletManager.wallet,
- // FIXME: move DemoHelper to Demo core module maybe
- isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId),
- )
- }.flatMap { fetchWalletStoreRentIfNeeded(walletStore, walletManager) }.flatMapOnFailure { error ->
- updateWalletStoreWithError(
- walletStore = walletStore,
- wallet = walletManager.wallet,
- error = error,
- )
- }
+ updateWalletManager(scanResponse, walletManager)
+ .map {
+ updateWalletManagerInStorage(userWalletId, walletManager)
+ }
+ .flatMap {
+ updateWalletStoreWithAmounts(
+ walletStore = walletStore,
+ updatedWallet = walletManager.wallet,
+ // FIXME: move DemoHelper to Demo core module maybe
+ isDemo = DemoHelper.isDemoCardId(scanResponse.card.cardId),
+ )
+ }
+ .flatMap {
+ fetchWalletStoreRentIfNeeded(walletStore, walletManager)
+ }
+ .flatMapOnFailure { error ->
+ updateWalletStoreWithError(
+ walletStore = walletStore,
+ wallet = walletManager.wallet,
+ error = error,
+ )
+ }
}
}
}
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 80c07fe5f7..877594d86e 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
@@ -34,7 +34,6 @@ import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTradeOrder
import com.tangem.tap.domain.walletconnect2.domain.models.binance.WcBinanceTransferOrder
import com.tangem.tap.features.details.redux.walletconnect.*
-import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
import com.tangem.tap.store
@@ -194,7 +193,12 @@ class WalletConnectSdkHelper {
val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message(), cardId = cardId)
return when (result) {
is CompletionResult.Success -> {
- HEX_PREFIX + result.data
+ HEX_PREFIX + EthereumUtils.prepareTransactionToSend(
+ signature = result.data.signature,
+ transactionToSign = dataToSign,
+ walletPublicKey = data.walletManager.wallet.publicKey,
+ blockchain = data.walletManager.wallet.blockchain,
+ ).toHexString()
}
is CompletionResult.Failure -> {
(result.error as? TangemSdkError)?.let { Analytics.send(WalletConnect.SignError(it)) }
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt
index 80d7ed0b09..01e3803798 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/WalletConnectRepositoryImpl.kt
@@ -1,15 +1,22 @@
package com.tangem.tap.domain.walletconnect2.data
-import android.app.*
-import com.tangem.tap.domain.walletconnect2.domain.*
+import android.app.Application
+import com.tangem.tap.domain.walletconnect2.domain.WalletConnectRepository
+import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer
import com.tangem.tap.domain.walletconnect2.domain.models.*
-import com.walletconnect.android.*
-import com.walletconnect.android.relay.*
-import com.walletconnect.web3.wallet.client.*
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-import timber.log.*
-import javax.inject.*
+import com.walletconnect.android.Core
+import com.walletconnect.android.CoreClient
+import com.walletconnect.android.relay.ConnectionType
+import com.walletconnect.web3.wallet.client.Wallet
+import com.walletconnect.web3.wallet.client.Web3Wallet
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import javax.inject.Inject
class WalletConnectRepositoryImpl @Inject constructor(
private val application: Application,
@@ -181,7 +188,7 @@ class WalletConnectRepositoryImpl @Inject constructor(
userNamespaces = userNamespaces,
)
if (missingNetworks.isNotEmpty()) {
- Timber.e("Unsupported blockchains: $missingNetworks")
+ Timber.e("Not added blockchains: $missingNetworks")
scope.launch {
_events.emit(
WalletConnectEvents.SessionApprovalError(
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt
index 03bd4e548e..ef202cd085 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt
@@ -1,13 +1,16 @@
package com.tangem.tap.domain.walletconnect2.domain
-import com.tangem.tap.common.extensions.*
-import com.tangem.tap.domain.walletconnect.*
+import com.tangem.tap.common.extensions.filterNotNull
+import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
import com.tangem.tap.domain.walletconnect2.domain.models.*
-import com.tangem.tap.features.details.ui.walletconnect.*
-import com.tangem.utils.coroutines.*
-import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.*
-import timber.log.*
+import com.tangem.tap.features.details.ui.walletconnect.WcSessionForScreen
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import timber.log.Timber
class WalletConnectInteractor(
private val handler: WalletConnectEventsHandler,
@@ -34,11 +37,11 @@ class WalletConnectInteractor(
suspend fun startListening(userWalletId: String, cardId: String?) {
this.userWalletId = userWalletId
this.cardId = cardId
+ walletConnectRepository.updateSessions()
coroutineScope {
launch { subscribeToEvents() }
launch { subscribeToSessions() }
}
- walletConnectRepository.updateSessions()
}
private suspend fun subscribeToEvents() {
@@ -47,6 +50,14 @@ class WalletConnectInteractor(
when (wcEvent) {
is WalletConnectEvents.SessionProposal -> {
Timber.d("WC session proposal event received")
+ val unsupportedNetworks = wcEvent.chainIds
+ .filter { blockchainHelper.chainIdToNetworkIdOrNull(it) == null }
+ if (unsupportedNetworks.isNotEmpty()) {
+ val error = WalletConnectError.ApprovalErrorUnsupportedNetwork(unsupportedNetworks)
+ handler.onSessionRejected(error)
+ return@onEach
+ }
+
val networksFormatted = wcEvent.chainIds
.mapNotNull { blockchainHelper.chainIdToFullNameOrNull(it) }
.toString()
@@ -57,12 +68,7 @@ class WalletConnectInteractor(
is WalletConnectError.ApprovalErrorMissingNetworks -> {
val missingNetworks = wcEvent.error.missingChains
.map { blockchainHelper.chainIdToNetworkIdOrNull(it) }
- val containsUnsupportedNetworks = missingNetworks.any { it == null }
- if (containsUnsupportedNetworks) {
- WalletConnectError.ApprovalErrorUnsupportedNetwork
- } else {
- WalletConnectError.ApprovalErrorAddNetwork(missingNetworks.filterNotNull())
- }
+ WalletConnectError.ApprovalErrorAddNetwork(missingNetworks.filterNotNull())
}
else -> wcEvent.error
}
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt
index bc93e31462..fe67571134 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt
@@ -130,7 +130,8 @@ class WcSessionRequestConverter(
): String? {
return sessionsRepository.loadSessions(userWalletId)
.firstOrNull { it.topic == sessionRequest.topic }
- ?.accounts?.firstOrNull { it.chainId == sessionRequest.chainId && it.walletAddress == walletAddress }
- ?.derivationPath
+ ?.accounts?.firstOrNull {
+ it.chainId == sessionRequest.chainId && it.walletAddress.lowercase() == walletAddress?.lowercase()
+ }?.derivationPath
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt
index 1f9b7f6771..35da4b88f8 100644
--- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt
+++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt
@@ -3,7 +3,7 @@ package com.tangem.tap.domain.walletconnect2.domain.models
sealed class WalletConnectError : Exception() {
data class ApprovalErrorMissingNetworks(val missingChains: List) : WalletConnectError()
data class ApprovalErrorAddNetwork(val networks: List) : WalletConnectError()
- object ApprovalErrorUnsupportedNetwork : WalletConnectError()
+ data class ApprovalErrorUnsupportedNetwork(val unsupportedNetworks: List) : WalletConnectError()
data class ExternalApprovalError(override val message: String?) : WalletConnectError()
object WrongUserWallet : WalletConnectError()
object UnsupportedMethod : WalletConnectError()
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
index f4478fc4fc..a203e348dd 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
@@ -4,9 +4,10 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.transition.TransitionInflater
@@ -25,8 +26,6 @@ import dagger.hilt.android.AndroidEntryPoint
internal class AddCustomTokenFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
-
with(TransitionInflater.from(requireContext())) {
enterTransition = inflateTransition(R.transition.fade)
exitTransition = inflateTransition(R.transition.fade)
@@ -41,7 +40,10 @@ internal class AddCustomTokenFragment : Fragment() {
}
TangemTheme {
- AddCustomTokenScreen(stateHolder = viewModel.uiState)
+ AddCustomTokenScreen(
+ modifier = Modifier.systemBarsPadding(),
+ stateHolder = viewModel.uiState,
+ )
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
index e57d82cc15..55afd696af 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
@@ -8,11 +8,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.FabPosition
import androidx.compose.material.Scaffold
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
@@ -33,11 +29,12 @@ import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCu
[REDACTED_AUTHOR]
*/
@Composable
-internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) {
+internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content, modifier: Modifier = Modifier) {
BackHandler(onBack = state.onBackButtonClick)
var floatingButtonHeight by remember { mutableStateOf(0.dp) }
Scaffold(
+ modifier = modifier,
topBar = {
AddCustomTokenToolbar(
title = state.toolbar.title,
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
index 71d079e899..eb099e7ee8 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
@@ -1,6 +1,7 @@
package com.tangem.tap.features.customtoken.impl.presentation.ui
import androidx.compose.runtime.Composable
+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
@@ -15,10 +16,10 @@ import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTok
[REDACTED_AUTHOR]
*/
@Composable
-internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder) {
+internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder, modifier: Modifier = Modifier) {
when (stateHolder) {
- is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(state = stateHolder)
- is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(state = stateHolder)
+ is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(stateHolder, modifier)
+ is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(stateHolder, modifier)
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
index c23fa86835..9304675ad7 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
@@ -1,31 +1,11 @@
package com.tangem.tap.features.customtoken.impl.presentation.ui
import androidx.activity.compose.BackHandler
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.BottomSheetScaffold
-import androidx.compose.material.BottomSheetScaffoldState
-import androidx.compose.material.BottomSheetState
-import androidx.compose.material.BottomSheetValue
-import androidx.compose.material.Divider
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.FabPosition
-import androidx.compose.material.Text
-import androidx.compose.material.rememberBottomSheetScaffoldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.key
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
+import androidx.compose.material.*
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
@@ -59,7 +39,7 @@ import kotlinx.coroutines.launch
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
-internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent) {
+internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestContent, modifier: Modifier = Modifier) {
val coroutineScope = rememberCoroutineScope()
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(initialValue = BottomSheetValue.Collapsed),
@@ -77,6 +57,7 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
var floatingButtonHeight by remember { mutableStateOf(0.dp) }
BottomSheetScaffold(
+ modifier = modifier,
sheetContent = {
SheetContent(
coroutineScope = coroutineScope,
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
index 0051e2e6c4..0090ef8223 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
@@ -8,7 +8,6 @@ import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -161,7 +160,6 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
expanded = isExpanded && isEnabled,
onDismissRequest = { isExpanded = false },
) {
- FocusRequester
model.items.forEachIndexed { index, item ->
DropdownMenuItem(
onClick = {
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
index 1d886914c7..297bf37f54 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
@@ -41,6 +41,7 @@ import com.tangem.wallet.BuildConfig
import com.tangem.wallet.R
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.persistentListOf
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@@ -207,7 +208,8 @@ internal class AddCustomTokenViewModel @Inject constructor(
val defaultNetwork = createNetworkSelectorItem(blockchain = Blockchain.Unknown)
return listOf(defaultNetwork) + Blockchain.values()
.filter { blockchain ->
- reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true
+ reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true &&
+ blockchain != Blockchain.Cardano
}
.sortedBy(Blockchain::fullName)
.map(::createNetworkSelectorItem)
@@ -270,7 +272,9 @@ internal class AddCustomTokenViewModel @Inject constructor(
type = DerivationPathSelectorType.CUSTOM,
),
) + Blockchain.values()
- .filter { blockchain -> blockchain.isSupportedInApp() && !blockchain.isTestnet() }
+ .filter { blockchain ->
+ blockchain.isSupportedInApp() && !blockchain.isTestnet() && blockchain != Blockchain.Cardano
+ }
.sortedBy(Blockchain::fullName)
.map(::createDerivationPathSelectorAdditionalItem)
}
@@ -510,7 +514,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
val sameAddress = contractAddress == wrappedCurrency.token.contractAddress
val sameBlockchain =
Blockchain.fromNetworkId(networkSelectorValue.toNetworkId()) == wrappedCurrency.blockchain
- val isSameDerivationPath = getDerivationPath()?.rawPath == wrappedCurrency.derivationPath
+ val isSameDerivationPath = getDerivationPath().isSameDerivationPath(wrappedCurrency.derivationPath)
sameId && sameAddress && sameBlockchain && isSameDerivationPath
}
}
@@ -522,10 +526,15 @@ internal class AddCustomTokenViewModel @Inject constructor(
.filterIsInstance()
.any {
val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
- networkSelectorValue == it.blockchain && getDerivationPath()?.rawPath == it.derivationPath
+ networkSelectorValue == it.blockchain &&
+ getDerivationPath().isSameDerivationPath(it.derivationPath)
}
}
+ private fun DerivationPath?.isSameDerivationPath(rawDerivationPath: String?): Boolean {
+ return this == rawDerivationPath?.let { DerivationPath(it) }
+ }
+
private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
when {
isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> {
@@ -605,7 +614,11 @@ internal class AddCustomTokenViewModel @Inject constructor(
private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) {
fun onBackButtonClick() {
- featureRouter.popBackStack()
+ viewModelScope.launch(dispatchers.main) {
+ // need delay before close, cause crashed in compose PopUpMenu as
+ delay(timeMillis = 100)
+ featureRouter.popBackStack()
+ }
}
fun onContactAddressValueChange(enteredValue: String) {
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
index b83b2ad75a..c42aa9f320 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt
@@ -66,7 +66,7 @@ class WalletConnectMiddleware {
walletConnectManager.restoreSessions(action.scanResponse)
}
is WalletConnectAction.HandleDeepLink -> {
- if (!action.wcUri.isNullOrBlank() && WalletConnectManager.isCorrectWcUri(action.wcUri)) {
+ if (!action.wcUri.isNullOrBlank()) {
store.dispatchOnMain(WalletConnectAction.OpenSession(action.wcUri))
}
}
@@ -238,7 +238,7 @@ class WalletConnectMiddleware {
return
}
val blockchain = action.blockchain.guard {
- store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
+ store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork()))
return
}
val walletManager = getWalletManager(
@@ -298,19 +298,25 @@ class WalletConnectMiddleware {
),
)
}
-
- WalletConnectError.ApprovalErrorUnsupportedNetwork -> {
- store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
- }
- is WalletConnectError.ExternalApprovalError -> {
+ is WalletConnectError.ApprovalErrorUnsupportedNetwork -> {
store.dispatchOnMain(
GlobalAction.ShowDialog(
- AppDialog.SimpleOkWarningDialog(
- message = action.error.message ?: "",
- ),
+ WalletConnectDialog.UnsupportedNetwork(action.error.unsupportedNetworks),
),
)
}
+ is WalletConnectError.ExternalApprovalError -> {
+ val message = action.error.message
+ if (!message.isNullOrEmpty()) {
+ store.dispatchOnMain(
+ GlobalAction.ShowDialog(
+ AppDialog.SimpleOkWarningDialog(
+ message = message,
+ ),
+ ),
+ )
+ }
+ }
else -> Unit
}
}
@@ -327,7 +333,7 @@ class WalletConnectMiddleware {
scope.launch { walletConnectInteractor.continueWithRequest(action.sessionRequest) }
}
is WalletConnectAction.RejectUnsupportedRequest -> {
- store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
+ store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork()))
}
}
}
@@ -337,7 +343,7 @@ class WalletConnectMiddleware {
chainId = chainId,
peer = session.peerMeta,
).guard {
- store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork))
+ store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedNetwork()))
return
}
diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
index 71f4a14ca6..135f597dc1 100644
--- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
+++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt
@@ -91,7 +91,7 @@ data class WalletForSession(
sealed class WalletConnectDialog : StateDialog {
data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog()
object UnsupportedCard : WalletConnectDialog()
- object UnsupportedNetwork : WalletConnectDialog()
+ data class UnsupportedNetwork(val networks: List? = null) : WalletConnectDialog()
data class AddNetwork(val network: String) : WalletConnectDialog()
object OpeningSessionRejected : WalletConnectDialog()
object SessionTimeout : WalletConnectDialog()
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 a08b9af8b2..91a8e803b3 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
@@ -24,7 +24,7 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
+ setFitSystemWindows(fit = true)
activity?.onBackPressedDispatcher?.addCallback(
this,
object : OnBackPressedCallback(true) {
@@ -47,6 +47,11 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
scannerView?.stopCamera()
}
+ override fun onDestroy() {
+ super.onDestroy()
+ setFitSystemWindows(fit = false)
+ }
+
override fun onResume() {
super.onResume()
scannerView?.setResultHandler(this)
@@ -55,7 +60,7 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
override fun handleResult(result: Result) {
store.dispatch(NavigationAction.PopBackTo())
- activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, false) }
+ setFitSystemWindows(fit = false)
if (!result.text.isNullOrBlank()) {
store.dispatch(WalletConnectAction.OpenSession(result.text))
}
@@ -83,4 +88,10 @@ class QrScanFragment : Fragment(0), ZXingScannerView.ResultHandler {
private fun requestPermission() {
requestPermissions(arrayOf(Manifest.permission.CAMERA), CameraView.PERMISSION_REQUEST_CODE)
}
+
+ private fun setFitSystemWindows(fit: Boolean) {
+ activity?.window?.let {
+ WindowCompat.setDecorFitsSystemWindows(it, fit)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt
index 0883a97fd6..cd9ad2160d 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
@@ -7,7 +7,6 @@ import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
-import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.ToastNotificationAction
import com.tangem.tap.domain.TapError
@@ -35,15 +34,14 @@ data class PrepareSendScreen(
val tokenRate: BigDecimal? = null,
) : SendScreenAction
-// Address or PayId
-sealed class AddressPayIdActionUi : SendScreenActionUi {
- data class HandleUserInput(val data: String) : AddressPayIdActionUi()
- data class PasteAddressPayId(val data: String, val sourceType: AddressEntered.SourceType) : AddressPayIdActionUi()
- data class CheckClipboard(val data: String?) : AddressPayIdActionUi()
- data class CheckAddressPayId(val sourceType: AddressEntered.SourceType?) : AddressPayIdActionUi()
- data class SetTruncateHandler(val handler: (String) -> String) : AddressPayIdActionUi()
- data class TruncateOrRestore(val truncate: Boolean) : AddressPayIdActionUi()
- data class ChangePayIdState(val sendingToPayIdEnabled: Boolean) : AddressPayIdActionUi()
+// Address
+sealed class AddressActionUi : SendScreenActionUi {
+ data class HandleUserInput(val data: String) : AddressActionUi()
+ data class PasteAddress(val data: String, val sourceType: AddressEntered.SourceType) : AddressActionUi()
+ data class CheckClipboard(val data: String?) : AddressActionUi()
+ data class CheckAddress(val sourceType: AddressEntered.SourceType?) : AddressActionUi()
+ data class SetTruncateHandler(val handler: (String) -> String) : AddressActionUi()
+ data class TruncateOrRestore(val truncate: Boolean) : AddressActionUi()
}
sealed class TransactionExtrasAction : SendScreenActionUi {
@@ -77,27 +75,15 @@ sealed class TransactionExtrasAction : SendScreenActionUi {
}
}
-sealed class AddressPayIdVerifyAction : SendScreenAction {
+sealed class AddressVerifyAction : SendScreenAction {
enum class Error {
- PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN,
- PAY_ID_NOT_REGISTERED,
- PAY_ID_REQUEST_FAILED,
ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN,
ADDRESS_SAME_AS_WALLET,
}
- data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressPayIdVerifyAction()
+ data class ChangePasteBtnEnableState(val isEnabled: Boolean) : AddressVerifyAction()
- sealed class PayIdVerification : AddressPayIdVerifyAction() {
- data class SetPayIdError(val error: Error?) : PayIdVerification()
- data class SetPayIdWalletAddress(
- val payId: String,
- val payIdWalletAddress: String,
- val isUserInput: Boolean,
- ) : PayIdVerification()
- }
-
- sealed class AddressVerification : AddressPayIdVerifyAction() {
+ sealed class AddressVerification : AddressVerifyAction() {
data class SetAddressError(val error: Error?) : AddressVerification()
data class SetWalletAddress(val address: String, val isUserInput: Boolean) : AddressVerification()
}
@@ -156,8 +142,6 @@ sealed class SendAction : SendScreenAction {
override val messageResource: Int = R.string.send_transaction_success
}
- data class SendError(override val error: TapError) : SendAction(), ErrorAction
-
sealed class Dialog : SendAction(), StateDialog {
data class TezosWarningDialog(
val reduceCallback: () -> Unit,
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt
similarity index 50%
rename from app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt
rename to app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt
index 406eacd087..576497222e 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressPayIdMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/AddressMiddleware.kt
@@ -2,49 +2,39 @@ package com.tangem.tap.features.send.redux.middlewares
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Wallet
-import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.domain.PayIdManager
-import com.tangem.tap.domain.isPayIdSupported
import com.tangem.tap.features.send.redux.*
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetAddressError
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification.SetWalletAddress
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdError
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification.SetPayIdWalletAddress
-import com.tangem.tap.scope
-import com.tangem.tap.store
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification.SetAddressError
+import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification.SetWalletAddress
+import com.tangem.tap.features.send.redux.AddressVerifyAction.Error
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
/**
[REDACTED_AUTHOR]
*/
-internal class AddressPayIdMiddleware {
+internal class AddressMiddleware {
- fun handle(action: AddressPayIdActionUi, appState: AppState?, dispatch: (Action) -> Unit) {
+ fun handle(action: AddressActionUi, appState: AppState?, dispatch: (Action) -> Unit) {
when (action) {
- is AddressPayIdActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch)
- is AddressPayIdActionUi.PasteAddressPayId -> pasteAddressPayId(action.data, action.sourceType, dispatch)
- is AddressPayIdActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch)
- is AddressPayIdActionUi.CheckAddressPayId -> verifyAddressPayId(action.sourceType, appState, dispatch)
+ is AddressActionUi.HandleUserInput -> handleUserInput(action.data, appState, dispatch)
+ is AddressActionUi.PasteAddress -> pasteAddress(action.data, action.sourceType, dispatch)
+ is AddressActionUi.CheckClipboard -> verifyClipboard(action.data, appState, dispatch)
+ is AddressActionUi.CheckAddress -> verifyAddress(action.sourceType, appState, dispatch)
else -> return
}
}
private fun handleUserInput(input: String, appState: AppState?, dispatch: DispatchFunction) {
val sendState = appState?.sendState ?: return
- if (input == sendState.addressPayIdState.viewFieldValue.value) return
+ if (input == sendState.addressState.viewFieldValue.value) return
setAddressAndCheck(data = input, sourceType = null, isUserInput = true, dispatch = dispatch)
}
- private fun pasteAddressPayId(data: String, sourceType: AddressEntered.SourceType, dispatch: (Action) -> Unit) {
+ private fun pasteAddress(data: String, sourceType: AddressEntered.SourceType, dispatch: (Action) -> Unit) {
setAddressAndCheck(data = data, sourceType = sourceType, isUserInput = false, dispatch = dispatch)
}
@@ -54,74 +44,27 @@ internal class AddressPayIdMiddleware {
isUserInput: Boolean,
dispatch: (Action) -> Unit,
) {
- val potentialPayId = data.lowercase()
- if (isPayIdEnabled() && PayIdManager.isPayId(potentialPayId)) {
- dispatch(SetPayIdWalletAddress(potentialPayId, "", isUserInput))
- } else {
- dispatch(SetWalletAddress(data, isUserInput))
- }
- dispatch(AddressPayIdActionUi.CheckAddressPayId(sourceType))
+ dispatch(SetWalletAddress(data, isUserInput))
+ dispatch(AddressActionUi.CheckAddress(sourceType))
}
- private fun verifyAddressPayId(
+ private fun verifyAddress(
sourceType: AddressEntered.SourceType?,
appState: AppState?,
dispatch: (Action) -> Unit,
) {
val sendState = appState?.sendState ?: return
val wallet = sendState.walletManager?.wallet ?: return
- val addressPayId = sendState.addressPayIdState.normalFieldValue ?: return
- val isUserInput = sendState.addressPayIdState.viewFieldValue.isFromUserInput
+ val address = sendState.addressState.normalFieldValue ?: return
+ val isUserInput = sendState.addressState.viewFieldValue.isFromUserInput
- if (isPayIdEnabled() && PayIdManager.isPayId(addressPayId)) {
- verifyPayId(addressPayId, wallet, isUserInput, dispatch)
- } else {
- verifyAddress(
- address = addressPayId,
- wallet = wallet,
- isUserInput = isUserInput,
- dispatch = dispatch,
- sourceType = sourceType,
- )
- }
- }
-
- private fun verifyPayId(payId: String, wallet: Wallet, isUserInput: Boolean, dispatch: DispatchFunction) {
- val blockchain = wallet.blockchain
- if (!blockchain.isPayIdSupported()) {
- dispatch(SetPayIdError(Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN))
- return
- }
-
- scope.launch {
- val result = PayIdManager().verifyPayId(payId, blockchain)
- withContext(Dispatchers.Main) {
- when (result) {
- is Result.Success -> {
- val addressDetails = result.data.getAddressDetails()
- if (addressDetails == null) {
- dispatch(SetPayIdError(Error.PAY_ID_NOT_REGISTERED))
- return@withContext
- }
-
- val address = addressDetails.address
- val failReason = isValidBlockchainAddressAndNotTheSameAsWallet(wallet, address)
- if (failReason == null) {
- dispatch(SetPayIdWalletAddress(payId, address, isUserInput))
- dispatch(TransactionExtrasAction.Prepare(wallet.blockchain, address, addressDetails.tag))
- dispatch(FeeAction.RequestFee)
- } else {
- dispatch(SetAddressError(failReason))
- dispatch(TransactionExtrasAction.Release)
- }
- }
- is Result.Failure -> {
- dispatch(SetPayIdError(Error.PAY_ID_REQUEST_FAILED))
- dispatch(TransactionExtrasAction.Release)
- }
- }
- }
- }
+ verifyAddress(
+ address = address,
+ wallet = wallet,
+ isUserInput = isUserInput,
+ dispatch = dispatch,
+ sourceType = sourceType,
+ )
}
private fun verifyAddress(
@@ -219,41 +162,33 @@ internal class AddressPayIdMiddleware {
}
private fun verifyClipboard(input: String?, appState: AppState?, dispatch: DispatchFunction) {
- val addressPayId = input ?: return
+ val address = input ?: return
val wallet = appState?.sendState?.walletManager?.wallet ?: return
val internalDispatcher: (Action) -> Unit = {
when (it) {
- is SetWalletAddress, is SetPayIdWalletAddress -> {
- dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(true))
+ is SetWalletAddress -> {
+ dispatch(AddressVerifyAction.ChangePasteBtnEnableState(true))
}
- is SetAddressError, is SetPayIdError -> {
- dispatch(AddressPayIdVerifyAction.ChangePasteBtnEnableState(false))
+ is SetAddressError -> {
+ dispatch(AddressVerifyAction.ChangePasteBtnEnableState(false))
}
}
}
- if (PayIdManager.isPayId(addressPayId) && isPayIdEnabled()) {
- verifyPayId(addressPayId, wallet, false, internalDispatcher)
- } else {
- verifyAddress(
- address = addressPayId,
- wallet = wallet,
- sourceType = null,
- isUserInput = false,
- dispatch = internalDispatcher,
- )
- }
- }
-
- private fun isPayIdEnabled(): Boolean {
- return store.state.globalState.configManager?.config?.isSendingToPayIdEnabled ?: false
+ verifyAddress(
+ address = address,
+ wallet = wallet,
+ sourceType = null,
+ isUserInput = false,
+ dispatch = internalDispatcher,
+ )
}
}
fun String.splitToMap(firstDelimiter: String, secondDelimiter: String): Map {
- return this.split(firstDelimiter)
+ return this
+ .split(firstDelimiter)
.map { it.split(secondDelimiter) }
- .map { it.first() to it.last().toString() }
- .toMap()
+ .associate { it.first() to it.last() }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/RequestFeeMiddleware.kt
index 1277c0993b..1b6769f0a8 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
@@ -39,7 +39,7 @@ class RequestFeeMiddleware {
}
val typedAmount = sendState.amountState.amountToExtract ?: return
- val destinationAddress = sendState.addressPayIdState.destinationWalletAddress!!
+ val destinationAddress = sendState.addressState.destinationWalletAddress!!
val destinationAmount = Amount(typedAmount, sendState.amountState.amountToSendCrypto)
val txSender = if (scanResponse.isDemoCard()) {
DemoTransactionSender(walletManager)
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 74a835349d..f48eec112c 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
@@ -6,7 +6,7 @@ import com.tangem.blockchain.blockchains.cosmos.CosmosTransactionExtras
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
+import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder.XrpTransactionExtras
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.SimpleResult
@@ -56,18 +56,17 @@ class SendMiddleware {
{ nextDispatch ->
{ action ->
when (action) {
- is AddressPayIdActionUi -> AddressPayIdMiddleware().handle(action, appState(), dispatch)
+ is AddressActionUi -> AddressMiddleware().handle(action, appState(), dispatch)
is AmountActionUi -> AmountMiddleware().handle(action, appState(), dispatch)
is RequestFee -> RequestFeeMiddleware().handle(appState(), dispatch)
is SendActionUi.SendAmountToRecipient ->
verifyAndSendTransaction(action, appState(), dispatch)
- is PrepareSendScreen -> setIfSendingToPayIdEnabled(appState(), dispatch)
is SendAction.Warnings.Update -> updateWarnings(dispatch)
is SendActionUi.CheckIfTransactionDataWasProvided -> {
val transactionData = appState()?.sendState?.externalTransactionData
if (transactionData != null) {
store.dispatchOnMain(
- AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
+ AddressVerifyAction.AddressVerification.SetWalletAddress(
address = transactionData.destinationAddress,
isUserInput = false,
),
@@ -97,7 +96,7 @@ private fun verifyAndSendTransaction(
val sendState = appState?.sendState ?: return
val walletManager = sendState.walletManager ?: return
val card = appState.globalState.scanResponse?.card ?: return
- val destinationAddress = sendState.addressPayIdState.destinationWalletAddress ?: return
+ val destinationAddress = sendState.addressState.destinationWalletAddress ?: return
val typedAmount = sendState.amountState.amountToExtract ?: return
val fee = sendState.feeState.currentFee ?: return
@@ -169,9 +168,7 @@ private fun sendTransaction(
transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) }
transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) }
- transactionExtras.xrpDestinationTag?.tag?.let {
- txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it))
- }
+ transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionExtras(it)) }
transactionExtras.cosmosMemoState?.memo?.let { txData = txData.copy(extras = CosmosTransactionExtras(it)) }
transactionExtras.tonMemoState?.memo?.let { txData = txData.copy(extras = TonTransactionExtras(it)) }
@@ -251,17 +248,10 @@ private fun sendTransaction(
Analytics.send(
Basic.TransactionSent(
sentFrom = AnalyticsParam.TxSentFrom.Sell,
- memoType = if (txData.extras != null) MemoType.Full else MemoType.Empty,
- ),
- )
- Analytics.send(
- Token.Send.SelectedCurrency(
- currency = when (mainCurrencyType) {
- MainCurrencyType.FIAT -> CurrencyType.AppCurrency
- MainCurrencyType.CRYPTO -> CurrencyType.AppCurrency
- },
+ memoType = getMemoType(transactionExtras),
),
)
+ Analytics.sendSelectedCurrencyEvent(mainCurrencyType)
dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
} else {
Analytics.send(
@@ -271,9 +261,10 @@ private fun sendTransaction(
token = amountToSend.currencySymbol,
feeType = feeType.convertToAnalyticsFeeType(),
),
- memoType = if (txData.extras != null) MemoType.Full else MemoType.Empty,
+ memoType = getMemoType(transactionExtras),
),
)
+ Analytics.sendSelectedCurrencyEvent(mainCurrencyType)
dispatch(NavigationAction.PopBackTo())
}
scope.launch(Dispatchers.IO) {
@@ -337,6 +328,25 @@ private fun sendTransaction(
}
}
+private fun getMemoType(transactionExtras: TransactionExtrasState): MemoType {
+ return when {
+ transactionExtras.isEmpty() -> MemoType.Empty
+ transactionExtras.isNull() -> MemoType.Null
+ else -> MemoType.Full
+ }
+}
+
+private fun Analytics.sendSelectedCurrencyEvent(mainCurrencyType: MainCurrencyType) {
+ send(
+ Token.Send.SelectedCurrency(
+ currency = when (mainCurrencyType) {
+ MainCurrencyType.FIAT -> CurrencyType.AppCurrency
+ MainCurrencyType.CRYPTO -> CurrencyType.Token
+ },
+ ),
+ )
+}
+
private fun updateFeedbackManagerInfo(
walletManager: WalletManager,
amountToSend: Amount,
@@ -381,12 +391,6 @@ fun createValidateTransactionError(
return TapError.ValidateTransactionErrors(tapErrors) { it.joinToString("\r\n") }
}
-private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -> Unit) {
- val isSendingToPayIdEnabled =
- appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false
- dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled))
-}
-
private fun updateWarnings(dispatch: (Action) -> Unit) {
val warningsManager = store.state.globalState.warningManager ?: return
val blockchain = store.state.sendState.walletManager?.wallet?.blockchain ?: return
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt
deleted file mode 100644
index ddacac0df8..0000000000
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressPayIdReducer.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.tangem.tap.features.send.redux.reducers
-
-import com.tangem.tap.features.send.redux.AddressPayIdActionUi
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.AddressVerification
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.PayIdVerification
-import com.tangem.tap.features.send.redux.SendScreenAction
-import com.tangem.tap.features.send.redux.states.AddressPayIdState
-import com.tangem.tap.features.send.redux.states.InputViewValue
-import com.tangem.tap.features.send.redux.states.SendState
-
-/**
-[REDACTED_AUTHOR]
- */
-class AddressPayIdReducer : SendInternalReducer {
- override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) {
- is AddressPayIdActionUi -> handleUiAction(action, sendState, sendState.addressPayIdState)
- is AddressPayIdVerifyAction -> handleAction(action, sendState, sendState.addressPayIdState)
- else -> sendState
- }
-
- private fun handleUiAction(
- action: AddressPayIdActionUi,
- sendState: SendState,
- state: AddressPayIdState,
- ): SendState {
- val result = when (action) {
- is AddressPayIdActionUi.HandleUserInput -> state
- is AddressPayIdActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler)
- is AddressPayIdActionUi.TruncateOrRestore -> {
- val value = if (action.truncate) state.truncatedFieldValue ?: "" else state.normalFieldValue ?: ""
- state.copy(viewFieldValue = state.viewFieldValue.copy(value = value))
- }
- is AddressPayIdActionUi.PasteAddressPayId -> return sendState
- is AddressPayIdActionUi.CheckClipboard -> return sendState
- is AddressPayIdActionUi.CheckAddressPayId -> return sendState
- is AddressPayIdActionUi.ChangePayIdState -> state.copy(sendingToPayIdEnabled = action.sendingToPayIdEnabled)
- }
- return updateLastState(sendState.copy(addressPayIdState = result), result)
- }
-
- private fun handleAction(
- action: AddressPayIdVerifyAction,
- sendState: SendState,
- state: AddressPayIdState,
- ): SendState {
- val result = when (action) {
- is PayIdVerification.SetPayIdWalletAddress -> {
- state.copy(
- viewFieldValue = InputViewValue(action.payId, action.isUserInput),
- normalFieldValue = action.payId,
- truncatedFieldValue = state.truncate(action.payId),
- destinationWalletAddress = action.payIdWalletAddress,
- error = null,
- )
- }
- is AddressVerification.SetWalletAddress -> {
- state.copy(
- viewFieldValue = InputViewValue(action.address, action.isUserInput),
- normalFieldValue = action.address,
- truncatedFieldValue = state.truncate(action.address),
- destinationWalletAddress = action.address,
- error = null,
- )
- }
- is AddressPayIdVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
- is AddressVerification.SetAddressError -> state.copy(error = action.error, destinationWalletAddress = null)
- is PayIdVerification.SetPayIdError -> state.copy(error = action.error, destinationWalletAddress = null)
- }
- return updateLastState(sendState.copy(addressPayIdState = result), result)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressReducer.kt
new file mode 100644
index 0000000000..a5b44bff0f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/AddressReducer.kt
@@ -0,0 +1,52 @@
+package com.tangem.tap.features.send.redux.reducers
+
+import com.tangem.tap.features.send.redux.AddressActionUi
+import com.tangem.tap.features.send.redux.AddressVerifyAction
+import com.tangem.tap.features.send.redux.AddressVerifyAction.AddressVerification
+import com.tangem.tap.features.send.redux.SendScreenAction
+import com.tangem.tap.features.send.redux.states.AddressState
+import com.tangem.tap.features.send.redux.states.InputViewValue
+import com.tangem.tap.features.send.redux.states.SendState
+
+/**
+[REDACTED_AUTHOR]
+ */
+class AddressReducer : SendInternalReducer {
+ override fun handle(action: SendScreenAction, sendState: SendState): SendState = when (action) {
+ is AddressActionUi -> handleUiAction(action, sendState, sendState.addressState)
+ is AddressVerifyAction -> handleAction(action, sendState, sendState.addressState)
+ else -> sendState
+ }
+
+ private fun handleUiAction(action: AddressActionUi, sendState: SendState, state: AddressState): SendState {
+ val result = when (action) {
+ is AddressActionUi.HandleUserInput -> state
+ is AddressActionUi.SetTruncateHandler -> state.copy(truncateHandler = action.handler)
+ is AddressActionUi.TruncateOrRestore -> {
+ val value = if (action.truncate) state.truncatedFieldValue ?: "" else state.normalFieldValue ?: ""
+ state.copy(viewFieldValue = state.viewFieldValue.copy(value = value))
+ }
+ is AddressActionUi.PasteAddress -> return sendState
+ is AddressActionUi.CheckClipboard -> return sendState
+ is AddressActionUi.CheckAddress -> return sendState
+ }
+ return updateLastState(sendState.copy(addressState = result), result)
+ }
+
+ private fun handleAction(action: AddressVerifyAction, sendState: SendState, state: AddressState): SendState {
+ val result = when (action) {
+ is AddressVerification.SetWalletAddress -> {
+ state.copy(
+ viewFieldValue = InputViewValue(action.address, action.isUserInput),
+ normalFieldValue = action.address,
+ truncatedFieldValue = state.truncate(action.address),
+ destinationWalletAddress = action.address,
+ error = null,
+ )
+ }
+ is AddressVerifyAction.ChangePasteBtnEnableState -> state.copy(pasteIsEnabled = action.isEnabled)
+ is AddressVerification.SetAddressError -> state.copy(error = action.error, destinationWalletAddress = null)
+ }
+ return updateLastState(sendState.copy(addressState = result), result)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
index 4134e8c148..463b0dcaea 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/SendScreenReducer.kt
@@ -25,7 +25,7 @@ object SendScreenReducer {
val reducer: SendInternalReducer = when (action) {
is PrepareSendScreen -> PrepareSendScreenStatesReducer()
- is AddressPayIdActionUi, is AddressPayIdVerifyAction -> AddressPayIdReducer()
+ is AddressActionUi, is AddressVerifyAction -> AddressReducer()
is TransactionExtrasAction -> TransactionExtrasReducer()
is AmountActionUi, is AmountAction -> AmountReducer()
is FeeActionUi, is FeeAction -> FeeReducer()
@@ -71,7 +71,7 @@ private class SendReducer : SendInternalReducer {
amountState = state.amountState.copy(
inputIsEnabled = false,
),
- addressPayIdState = state.addressPayIdState.copy(
+ addressState = state.addressState.copy(
inputIsEnabled = false,
),
)
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt
similarity index 80%
rename from app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt
rename to app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt
index b1e3261e67..b6b65a595f 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressState.kt
@@ -2,17 +2,16 @@ package com.tangem.tap.features.send.redux.states
import androidx.core.text.isDigitsOnly
import com.tangem.blockchain.blockchains.stellar.StellarMemo
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
+import com.tangem.tap.features.send.redux.AddressVerifyAction
import java.math.BigInteger
-data class AddressPayIdState(
+data class AddressState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val normalFieldValue: String? = null,
val truncatedFieldValue: String? = null,
val destinationWalletAddress: String? = null,
- val error: AddressPayIdVerifyAction.Error? = null,
+ val error: AddressVerifyAction.Error? = null,
val truncateHandler: ((String) -> String)? = null,
- val sendingToPayIdEnabled: Boolean = false,
val pasteIsEnabled: Boolean = false,
val inputIsEnabled: Boolean = true,
) : SendScreenState {
@@ -22,8 +21,6 @@ data class AddressPayIdState(
fun truncate(value: String): String = truncateHandler?.invoke(value) ?: value
fun isReady(): Boolean = error == null && destinationWalletAddress?.isNotEmpty() ?: false
-
- fun isPayIdState(): Boolean = destinationWalletAddress != null && destinationWalletAddress != normalFieldValue
}
data class TransactionExtrasState(
@@ -34,6 +31,21 @@ data class TransactionExtrasState(
val cosmosMemoState: CosmosMemoState? = null,
) : IdStateHolder {
override val stateId: StateId = StateId.TRANSACTION_EXTRAS
+
+ fun isNull(): Boolean {
+ return xlmMemo == null && binanceMemo == null && xrpDestinationTag == null && tonMemoState == null &&
+ cosmosMemoState == null
+ }
+
+ fun isEmpty(): Boolean {
+ val isXlmEmpty = xlmMemo?.viewFieldValue?.value?.isEmpty() ?: false
+ val isBinanceEmpty = binanceMemo?.viewFieldValue?.value?.isEmpty() ?: false
+ val isXrpEmpty = xrpDestinationTag?.viewFieldValue?.value?.isEmpty() ?: false
+ val isTonEmpty = tonMemoState?.viewFieldValue?.value?.isEmpty() ?: false
+ val isCosmosEmpty = cosmosMemoState?.viewFieldValue?.value?.isEmpty() ?: false
+
+ return isXlmEmpty || isBinanceEmpty || isXrpEmpty || isTonEmpty || isCosmosEmpty
+ }
}
enum class XlmMemoType {
@@ -83,11 +95,7 @@ data class BinanceMemoState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val memo: BigInteger? = null,
val error: TransactionExtraError? = null,
-) {
- companion object {
- val MAX_NUMBER: BigInteger = BigInteger("FFFFFFFFFFFFFFFF", 16)
- }
-}
+)
// tag must contains only digits
data class XrpDestinationTagState(
@@ -105,6 +113,7 @@ data class TonMemoState(
val memo: String? = null,
val error: TransactionExtraError? = null,
)
+
data class CosmosMemoState(
val viewFieldValue: InputViewValue = InputViewValue(""),
val memo: String? = null,
diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
index 6a47e2d342..8400f07c87 100644
--- a/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/SendState.kt
@@ -34,7 +34,7 @@ data class SendState(
val coinConverter: CurrencyConverter? = null,
val tokenConverter: CurrencyConverter? = null,
val lastChangedStates: LinkedHashSet = linkedSetOf(),
- val addressPayIdState: AddressPayIdState = AddressPayIdState(),
+ val addressState: AddressState = AddressState(),
val transactionExtrasState: TransactionExtrasState = TransactionExtrasState(),
val amountState: AmountState = AmountState(),
val feeState: FeeState = FeeState(),
@@ -52,7 +52,7 @@ data class SendState(
MainCurrencyType.CRYPTO -> amountState.amountToExtract?.decimals ?: 0
}
- fun convertFiatToCoin(value: BigDecimal): BigDecimal {
+ private fun convertFiatToCoin(value: BigDecimal): BigDecimal {
return if (!this.coinIsConvertible()) value else coinConverter!!.toCrypto(value)
}
@@ -60,14 +60,14 @@ data class SendState(
return if (!this.tokenIsConvertible()) value else tokenConverter!!.toCrypto(value)
}
- fun convertCoinToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
+ private fun convertCoinToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
if (!this.coinIsConvertible()) return value
val converter = coinConverter!!
return if (!scaleWithPrecision) converter.toFiat(value) else converter.toFiatWithPrecision(value)
}
- fun convertTokenToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
+ private fun convertTokenToFiat(value: BigDecimal, scaleWithPrecision: Boolean = false): BigDecimal {
if (!this.tokenIsConvertible()) return value
val converter = tokenConverter!!
@@ -107,13 +107,13 @@ data class SendState(
}
companion object {
- fun addressPayIdIsReady(): Boolean = store.state.sendState.addressPayIdState.isReady()
+ private fun addressIsReady(): Boolean = store.state.sendState.addressState.isReady()
- fun amountIsReady(): Boolean = store.state.sendState.amountState.isReady()
+ private fun amountIsReady(): Boolean = store.state.sendState.amountState.isReady()
- fun isReadyToRequestFee(): Boolean = addressPayIdIsReady() && amountIsReady()
+ fun isReadyToRequestFee(): Boolean = addressIsReady() && amountIsReady()
- fun isReadyToSend(): Boolean = addressPayIdIsReady() && amountIsReady() &&
+ fun isReadyToSend(): Boolean = addressIsReady() && amountIsReady() &&
store.state.sendState.feeState.isReady()
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt b/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt
index d3b563025d..d93147b24b 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/EditTextCustomPaste.kt
@@ -5,14 +5,16 @@ import android.content.Context
import android.util.AttributeSet
import com.google.android.material.textfield.TextInputEditText
-class EditTextCustomPaste @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttr: Int = 0,
-) : TextInputEditText(context, attrs, defStyleAttr) {
+class EditTextCustomPaste : TextInputEditText {
private var onSystemPasteButtonClickListener: (() -> Unit)? = null
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
fun setOnSystemPasteButtonClickListener(callback: () -> Unit) {
onSystemPasteButtonClickListener = callback
}
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
index 64c4fc53a7..fec34d9996 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt
@@ -33,7 +33,7 @@ import com.tangem.tap.common.toggleWidget.ViewStateWidget
import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.send.redux.*
-import com.tangem.tap.features.send.redux.AddressPayIdActionUi.*
+import com.tangem.tap.features.send.redux.AddressActionUi.*
import com.tangem.tap.features.send.redux.AmountActionUi.*
import com.tangem.tap.features.send.redux.FeeActionUi.*
import com.tangem.tap.features.send.redux.states.FeeType
@@ -80,7 +80,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
etAmountToSend = view.findViewById(R.id.etAmountToSend)
initSendButtonStates()
- setupAddressOrPayIdLayout()
+ setupAddressLayout()
setupTransactionExtrasLayout()
setupAmountLayout()
setupFeeLayout()
@@ -99,14 +99,14 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
sendBtn = IndeterminateProgressButtonWidget(btnSend, progress)
}
- private fun setupAddressOrPayIdLayout() = with(binding.lSendAddressPayid) {
- store.dispatch(SetTruncateHandler { etAddressOrPayId.truncateMiddleWith(it, "...") })
+ private fun setupAddressLayout() = with(binding.lSendAddress) {
+ store.dispatch(SetTruncateHandler { etAddress.truncateMiddleWith(it, "...") })
store.dispatch(CheckClipboard(requireContext().getFromClipboard()?.toString()))
- etAddressOrPayId.apply {
+ etAddress.apply {
setOnSystemPasteButtonClickListener {
store.dispatch(
- PasteAddressPayId(
+ PasteAddress(
data = requireContext().getFromClipboard()?.toString() ?: "",
sourceType = Token.Send.AddressEntered.SourceType.PastePopup,
),
@@ -119,9 +119,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
- .filter { store.state.sendState.addressPayIdState.viewFieldValue.value != it }
+ .filter { store.state.sendState.addressState.viewFieldValue.value != it }
.onEach {
- store.dispatch(AddressPayIdActionUi.HandleUserInput(it))
+ store.dispatch(AddressActionUi.HandleUserInput(it))
}
.launchIn(mainScope)
}
@@ -129,12 +129,12 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
imvPaste.setOnClickListener {
Analytics.send(Token.Send.ButtonPaste())
store.dispatch(
- PasteAddressPayId(
+ PasteAddress(
data = requireContext().getFromClipboard()?.toString() ?: "",
sourceType = Token.Send.AddressEntered.SourceType.PasteButton,
),
)
- store.dispatch(TruncateOrRestore(!etAddressOrPayId.isFocused))
+ store.dispatch(TruncateOrRestore(!etAddress.isFocused))
}
imvQrCode.setOnClickListener {
Analytics.send(Token.Send.ButtonQRCode())
@@ -145,7 +145,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
}
}
- private fun setupTransactionExtrasLayout() = with(binding.lSendAddressPayid) {
+ private fun setupTransactionExtrasLayout() = with(binding.lSendAddress) {
// TODO: [REDACTED_TASK_KEY]
etXlmMemo.inputtedTextAsFlow()
.debounce(EDIT_TEXT_INPUT_DEBOUNCE)
@@ -202,15 +202,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
// Delayed launch is needed in order for the UI to be drawn and to process the sent events.
// If do not use the delay, then etAmount error field is not displayed when
// inserting an incorrect amount by shareUri
- binding.lSendAddressPayid.imvQrCode.postDelayed(
+ binding.lSendAddress.imvQrCode.postDelayed(
{
store.dispatch(
- PasteAddressPayId(
+ PasteAddress(
data = scannedCode,
sourceType = Token.Send.AddressEntered.SourceType.QRCode,
),
)
- store.dispatch(TruncateOrRestore(!binding.lSendAddressPayid.etAddressOrPayId.isFocused))
+ store.dispatch(TruncateOrRestore(!binding.lSendAddress.etAddress.isFocused))
},
200,
)
diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
index 12f16a0274..7c12e82496 100644
--- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
+++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt
@@ -7,30 +7,15 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.text.bold
import com.tangem.common.extensions.remove
-import com.tangem.tap.common.extensions.beginDelayedTransition
-import com.tangem.tap.common.extensions.enableError
-import com.tangem.tap.common.extensions.getColor
-import com.tangem.tap.common.extensions.getString
-import com.tangem.tap.common.extensions.hide
-import com.tangem.tap.common.extensions.show
-import com.tangem.tap.common.extensions.update
+import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.getMessageString
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.MultiMessageError
import com.tangem.tap.domain.assembleErrors
import com.tangem.tap.features.BaseStoreFragment
-import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
+import com.tangem.tap.features.send.redux.AddressVerifyAction.Error
import com.tangem.tap.features.send.redux.SendAction
-import com.tangem.tap.features.send.redux.states.AddressPayIdState
-import com.tangem.tap.features.send.redux.states.AmountState
-import com.tangem.tap.features.send.redux.states.FeeState
-import com.tangem.tap.features.send.redux.states.MainCurrencyType
-import com.tangem.tap.features.send.redux.states.ReceiptLayoutType
-import com.tangem.tap.features.send.redux.states.ReceiptState
-import com.tangem.tap.features.send.redux.states.SendState
-import com.tangem.tap.features.send.redux.states.StateId
-import com.tangem.tap.features.send.redux.states.TransactionExtraError
-import com.tangem.tap.features.send.redux.states.TransactionExtrasState
+import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.ui.FeeUiHelper
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.send.ui.dialogs.KaspaWarningDialog
@@ -61,7 +46,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
lastChangedStates.forEach {
when (it) {
StateId.SEND_SCREEN -> handleSendScreen(fg, state)
- StateId.ADDRESS_PAY_ID -> handleAddressPayIdState(fg, state.addressPayIdState)
+ StateId.ADDRESS_PAY_ID -> handleAddressState(fg, state.addressState)
StateId.TRANSACTION_EXTRAS -> handleTransactionExtrasState(fg, state.transactionExtrasState)
StateId.AMOUNT -> handleAmountState(fg, state.amountState)
StateId.FEE -> handleFeeState(fg, state.feeState)
@@ -72,7 +57,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
@Suppress("ComplexMethod")
private fun handleTransactionExtrasState(fg: SendFragment, infoState: TransactionExtrasState) =
- with(fg.binding.lSendAddressPayid) {
+ with(fg.binding.lSendAddress) {
fun showView(view: View, info: Any?) {
view.show(info != null)
}
@@ -192,13 +177,10 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
)
}
- private fun handleAddressPayIdState(fg: SendFragment, state: AddressPayIdState) =
- with(fg.binding.lSendAddressPayid) {
+ private fun handleAddressState(fg: SendFragment, state: AddressState) {
+ with(fg.binding.lSendAddress) {
fun parseError(context: Context, error: Error?): String? {
val resId = when (error) {
- Error.PAY_ID_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_error_payid_unsupported_by_blockchain
- Error.PAY_ID_NOT_REGISTERED -> R.string.send_error_payid_not_registered
- Error.PAY_ID_REQUEST_FAILED -> R.string.send_error_payid_request_failed
Error.ADDRESS_INVALID_OR_UNSUPPORTED_BY_BLOCKCHAIN -> R.string.send_validation_invalid_address
Error.ADDRESS_SAME_AS_WALLET -> R.string.send_error_address_same_as_wallet
else -> null
@@ -208,8 +190,8 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
imvPaste.isEnabled = state.pasteIsEnabled
- val et = etAddressOrPayId
- val til = tilAddressOrPayId
+ val et = etAddress
+ val til = tilAddress
val parsedError = parseError(til.context, state.error)
til.isEnabled = state.inputIsEnabled
@@ -218,19 +200,15 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber
flPaste.show(state.inputIsEnabled)
flQrCode.show(state.inputIsEnabled)
- val hintResId = if (state.sendingToPayIdEnabled) {
- R.string.send_destination_hint_address_payid
- } else {
- R.string.send_destination_hint_address
- }
- til.hint = til.getString(hintResId)
+ til.hint = til.getString(R.string.send_destination_hint_address)
til.error = parsedError
til.isErrorEnabled = parsedError != null
til.helperText = state.destinationWalletAddress
- til.isHelperTextEnabled = state.isPayIdState() && parsedError == null
+ til.isHelperTextEnabled = parsedError == null
if (!state.viewFieldValue.isFromUserInput) et.update(state.viewFieldValue.value)
}
+ }
private fun handleAmountState(fg: SendFragment, state: AmountState) = with(fg.binding.lSendAmount) {
if (state.error != null) {
diff --git a/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt b/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt
new file mode 100644
index 0000000000..74505b69d3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/data/DefaultShopRepository.kt
@@ -0,0 +1,37 @@
+package com.tangem.tap.features.shop.data
+
+import com.tangem.datasource.api.tangemTech.TangemTechApi
+import com.tangem.datasource.api.tangemTech.models.ShopResponse
+import com.tangem.tap.features.shop.domain.ShopRepository
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import com.tangem.utils.coroutines.runCatching
+import timber.log.Timber
+
+/**
+ * Default implementation of shop feature repository
+ *
+ * @property tangemTechApi TangemTech API
+ * @property dispatchers coroutine dispatchers provider
+ *
+[REDACTED_AUTHOR]
+ */
+internal class DefaultShopRepository(
+ private val tangemTechApi: TangemTechApi,
+ private val dispatchers: CoroutineDispatcherProvider,
+) : ShopRepository {
+
+ override suspend fun isShopifyOrderingAvailable(): Boolean {
+ return runCatching(dispatchers.io) { tangemTechApi.getShopInfo(name = SHOPIFY_NAME) }
+ .fold(
+ onSuccess = ShopResponse::isOrderingAvailable,
+ onFailure = {
+ Timber.e("Server error. isShopifyOrderingAvailable returns default value (true)")
+ true
+ },
+ )
+ }
+
+ private companion object {
+ const val SHOPIFY_NAME = "shopify"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt b/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt
new file mode 100644
index 0000000000..2c00aa1181
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/di/ShopUseCaseModule.kt
@@ -0,0 +1,28 @@
+package com.tangem.tap.features.shop.di
+
+import com.tangem.datasource.api.tangemTech.TangemTechApi
+import com.tangem.tap.features.shop.data.DefaultShopRepository
+import com.tangem.tap.features.shop.domain.DefaultShopifyOrderingAvailabilityUseCase
+import com.tangem.tap.features.shop.domain.ShopifyOrderingAvailabilityUseCase
+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 ShopUseCaseModule {
+
+ @Provides
+ @ViewModelScoped
+ fun provideShopifyOrderingAvailabilityUseCase(
+ tangemTechApi: TangemTechApi,
+ dispatchers: CoroutineDispatcherProvider,
+ ): ShopifyOrderingAvailabilityUseCase {
+ return DefaultShopifyOrderingAvailabilityUseCase(
+ shopRepository = DefaultShopRepository(tangemTechApi, dispatchers),
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt
new file mode 100644
index 0000000000..41bbe3d449
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/domain/DefaultShopifyOrderingAvailabilityUseCase.kt
@@ -0,0 +1,15 @@
+package com.tangem.tap.features.shop.domain
+
+/**
+ * Default implementation of use case to define shopify ordering availability
+ *
+ * @property shopRepository shop feature repository
+ *
+[REDACTED_AUTHOR]
+ */
+internal class DefaultShopifyOrderingAvailabilityUseCase(
+ private val shopRepository: ShopRepository,
+) : ShopifyOrderingAvailabilityUseCase {
+
+ override suspend fun invoke() = shopRepository.isShopifyOrderingAvailable()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt
new file mode 100644
index 0000000000..c8cd6f3780
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopRepository.kt
@@ -0,0 +1,12 @@
+package com.tangem.tap.features.shop.domain
+
+/**
+ * Shop feature repository
+ *
+[REDACTED_AUTHOR]
+ */
+internal interface ShopRepository {
+
+ /** Get shopify ordering availability */
+ suspend fun isShopifyOrderingAvailable(): Boolean
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt
new file mode 100644
index 0000000000..5f6c351ce2
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/domain/ShopifyOrderingAvailabilityUseCase.kt
@@ -0,0 +1,12 @@
+package com.tangem.tap.features.shop.domain
+
+/**
+ * Use case to define shopify ordering availability
+ *
+[REDACTED_AUTHOR]
+ */
+internal interface ShopifyOrderingAvailabilityUseCase {
+
+ /** Get availability */
+ suspend operator fun invoke(): Boolean
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt b/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt
new file mode 100644
index 0000000000..34966466cc
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/shop/presentation/ShopViewModel.kt
@@ -0,0 +1,39 @@
+package com.tangem.tap.features.shop.presentation
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.tangem.tap.features.shop.domain.ShopifyOrderingAvailabilityUseCase
+import com.tangem.tap.features.shop.redux.ShopAction
+import com.tangem.tap.proxy.AppStateHolder
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import com.tangem.utils.coroutines.runCatching
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+/**
+ * Shop screen view model
+ *
+ * @property shopifyOrderingAvailabilityUseCase use case to define shopify ordering availability
+ * @property dispatchers coroutine dispatchers provider
+ * @property appStateHolder redux state holder
+ *
+[REDACTED_AUTHOR]
+ */
+@HiltViewModel
+internal class ShopViewModel @Inject constructor(
+ private val shopifyOrderingAvailabilityUseCase: ShopifyOrderingAvailabilityUseCase,
+ private val dispatchers: CoroutineDispatcherProvider,
+ private val appStateHolder: AppStateHolder,
+) : ViewModel() {
+
+ /** Check ordering delay block visibility */
+ fun checkOrderingDelayBlockVisibility() {
+ viewModelScope.launch(dispatchers.main) {
+ val visibility = runCatching(dispatchers.io) { shopifyOrderingAvailabilityUseCase() }
+ .fold(onSuccess = { !it }, onFailure = { false })
+
+ appStateHolder.mainStore?.dispatch(action = ShopAction.SetOrderingDelayBlockVisibility(visibility))
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt
index 219753c469..54c6bcad43 100644
--- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopAction.kt
@@ -8,38 +8,40 @@ import com.tangem.tap.common.shop.googlepay.GooglePayService
import com.tangem.wallet.R
import org.rekotlin.Action
-sealed class ShopAction : Action {
+sealed interface ShopAction : Action {
- object LoadProducts : ShopAction() {
- data class Success(val products: List) : ShopAction()
- object Failure : ShopAction(), NotificationAction {
+ object LoadProducts : ShopAction {
+ data class Success(val products: List) : ShopAction
+ object Failure : ShopAction, NotificationAction {
override val messageResource = R.string.common_server_unavailable
}
}
- data class ApplyPromoCode(val promoCode: String) : ShopAction() {
- data class Success(val promoCode: String?, val products: List) : ShopAction()
- object InvalidPromoCode : ShopAction()
+ data class ApplyPromoCode(val promoCode: String) : ShopAction {
+ data class Success(val promoCode: String?, val products: List) : ShopAction
+ object InvalidPromoCode : ShopAction
}
- object BuyWithGooglePay : ShopAction() {
- object UserCancelled : ShopAction()
- data class HandleGooglePayResponse(val resultCode: Int, val data: Intent?) : ShopAction()
+ object BuyWithGooglePay : ShopAction {
+ object UserCancelled : ShopAction
+ data class HandleGooglePayResponse(val resultCode: Int, val data: Intent?) : ShopAction
- data class Failure(val exception: Throwable) : ShopAction()
- object Success : ShopAction()
+ data class Failure(val exception: Throwable) : ShopAction
+ object Success : ShopAction
}
- object StartWebCheckout : ShopAction()
+ object StartWebCheckout : ShopAction
- data class CheckIfGooglePayAvailable(val googlePayService: GooglePayService) : ShopAction() {
- object Success : ShopAction()
- object Failure : ShopAction()
+ data class CheckIfGooglePayAvailable(val googlePayService: GooglePayService) : ShopAction {
+ object Success : ShopAction
+ object Failure : ShopAction
}
- data class SelectProduct(val productType: ProductType) : ShopAction()
+ data class SelectProduct(val productType: ProductType) : ShopAction
- object FinishSuccessfulOrder : ShopAction()
+ object FinishSuccessfulOrder : ShopAction
- object ResetState : ShopAction()
+ object ResetState : ShopAction
+
+ data class SetOrderingDelayBlockVisibility(val visibility: Boolean) : ShopAction
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt
index 8a97a1a58c..60cb0667ad 100644
--- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopReducer.kt
@@ -6,63 +6,38 @@ object ShopReducer {
fun reduce(action: Action, state: ShopState): ShopState = internalReduce(action, state)
}
-@Suppress("ComplexMethod")
private fun internalReduce(action: Action, state: ShopState): ShopState {
if (action !is ShopAction) return state
return when (action) {
- is ShopAction.ApplyPromoCode -> state.copy(
- promoCode = action.promoCode,
- promoCodeLoading = true,
- )
- ShopAction.BuyWithGooglePay -> state
- ShopAction.LoadProducts -> state
- is ShopAction.LoadProducts.Success -> {
- state.copy(
- availableProducts = action.products,
- )
- }
- ShopAction.StartWebCheckout -> state
- ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy(
- promoCode = null,
- promoCodeLoading = false,
- )
+ is ShopAction.ApplyPromoCode -> state.copy(promoCode = action.promoCode, promoCodeLoading = true)
+ is ShopAction.LoadProducts.Success -> state.copy(availableProducts = action.products)
+ is ShopAction.ApplyPromoCode.InvalidPromoCode -> state.copy(promoCode = null, promoCodeLoading = false)
is ShopAction.ApplyPromoCode.Success -> {
state.copy(
promoCode = action.promoCode,
availableProducts = action.products,
promoCodeLoading = false,
+ )
+ }
+ is ShopAction.SelectProduct -> state.copy(selectedProduct = action.productType)
- )
- }
- is ShopAction.SelectProduct -> {
- state.copy(
- selectedProduct = action.productType,
- )
- }
- is ShopAction.CheckIfGooglePayAvailable -> {
- state
- }
- ShopAction.CheckIfGooglePayAvailable.Failure -> {
- state.copy(isGooglePayAvailable = false)
- }
- ShopAction.CheckIfGooglePayAvailable.Success -> {
- state.copy(isGooglePayAvailable = false) // TODO: change when we add support for GPay
- }
- is ShopAction.BuyWithGooglePay.Failure -> {
- state
- }
- is ShopAction.BuyWithGooglePay.HandleGooglePayResponse -> {
- state
- }
- ShopAction.BuyWithGooglePay.Success -> {
- state
- }
- ShopAction.BuyWithGooglePay.UserCancelled -> {
- state
- }
- ShopAction.FinishSuccessfulOrder -> state
- ShopAction.ResetState -> ShopState()
- ShopAction.LoadProducts.Failure -> state
+ // TODO: change when we add support for GPay
+ is ShopAction.CheckIfGooglePayAvailable.Failure -> state.copy(isGooglePayAvailable = false)
+ is ShopAction.CheckIfGooglePayAvailable.Success -> state.copy(isGooglePayAvailable = false)
+
+ is ShopAction.ResetState -> ShopState()
+ is ShopAction.SetOrderingDelayBlockVisibility -> state.copy(isOrderingDelayBlockVisible = action.visibility)
+ is ShopAction.BuyWithGooglePay,
+ is ShopAction.LoadProducts,
+ is ShopAction.StartWebCheckout,
+ is ShopAction.CheckIfGooglePayAvailable,
+ is ShopAction.BuyWithGooglePay.Failure,
+ is ShopAction.BuyWithGooglePay.HandleGooglePayResponse,
+ is ShopAction.BuyWithGooglePay.Success,
+ is ShopAction.BuyWithGooglePay.UserCancelled,
+ is ShopAction.FinishSuccessfulOrder,
+ is ShopAction.LoadProducts.Failure,
+ -> state
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt
index ebbbb7cb79..491e961e58 100644
--- a/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt
+++ b/app/src/main/java/com/tangem/tap/features/shop/redux/ShopState.kt
@@ -10,6 +10,7 @@ data class ShopState(
val promoCode: String? = null,
val promoCodeLoading: Boolean = false,
val isGooglePayAvailable: Boolean = false, // TODO: change when we add support for GPay
+ val isOrderingDelayBlockVisible: Boolean = false,
) : StateType {
val total: String?
get() = availableProducts.firstOrNull { it.type == selectedProduct }?.totalSum?.finalValue
diff --git a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt
index dccbe4d7b7..56ffe54b0f 100644
--- a/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/shop/ui/ShopFragment.kt
@@ -9,28 +9,35 @@ import android.view.View.OnFocusChangeListener
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.activity.OnBackPressedCallback
+import androidx.fragment.app.viewModels
import by.kirich1409.viewbindingdelegate.viewBinding
import com.tangem.tap.common.GlobalLayoutStateHandler
import com.tangem.tap.common.KeyboardObserver
import com.tangem.tap.common.extensions.getQuantityString
+import com.tangem.tap.common.extensions.hide
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.common.shop.data.ProductType
import com.tangem.tap.features.BaseStoreFragment
+import com.tangem.tap.features.shop.presentation.ShopViewModel
import com.tangem.tap.features.shop.redux.ShopAction
import com.tangem.tap.features.shop.redux.ShopState
import com.tangem.tap.store
import com.tangem.wallet.R
import com.tangem.wallet.databinding.FragmentShopBinding
+import dagger.hilt.android.AndroidEntryPoint
import org.rekotlin.StoreSubscriber
-class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber {
+@AndroidEntryPoint
+internal class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber {
private val binding: FragmentShopBinding by viewBinding(FragmentShopBinding::bind)
private var cardTranslationY = 70f
private lateinit var keyboardObserver: KeyboardObserver
+ private val viewModel by viewModels()
+
override fun subscribeToStore() {
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
@@ -43,6 +50,8 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ viewModel.checkOrderingDelayBlockVisibility()
+
activity?.onBackPressedDispatcher?.addCallback(
this,
object : OnBackPressedCallback(true) {
@@ -133,6 +142,7 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
animateProductSelection(state.selectedProduct)
handlePriceState(state)
handlePromoCodeState(state)
+ handleOrderingDelayBlock(isVisible = state.isOrderingDelayBlockVisible)
handleButtonsState(state)
}
@@ -173,6 +183,10 @@ class ShopFragment : BaseStoreFragment(R.layout.fragment_shop), StoreSubscriber<
pbPromoCode.show(state.promoCodeLoading)
}
+ private fun handleOrderingDelayBlock(isVisible: Boolean) {
+ if (isVisible) binding.tvSoldOutDesc.show() else binding.tvSoldOutDesc.hide()
+ }
+
private fun handleButtonsState(state: ShopState) = with(binding) {
btnPayGooglePay.root.show(state.isGooglePayAvailable)
btnAlternativePayment.show(state.isGooglePayAvailable)
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 e5ac57cfc8..8af509f84f 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
@@ -4,9 +4,10 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.transition.TransitionInflater
@@ -25,8 +26,6 @@ import dagger.hilt.android.AndroidEntryPoint
internal class TokensListFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
-
with(TransitionInflater.from(requireContext())) {
enterTransition = inflateTransition(R.transition.fade)
exitTransition = inflateTransition(R.transition.fade)
@@ -41,7 +40,10 @@ internal class TokensListFragment : Fragment() {
}
TangemTheme {
- TokensListScreen(stateHolder = viewModel.uiState)
+ TokensListScreen(
+ modifier = Modifier.systemBarsPadding(),
+ stateHolder = viewModel.uiState,
+ )
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt
index df128ac9b4..790a83fb5f 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokenItemState.kt
@@ -1,12 +1,16 @@
package com.tangem.tap.features.tokens.impl.presentation.states
+import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList
/**
- * Network item state
+ * Token item state.
+ * All subclasses is stable, but @Immutable annotation is required to use this sealed class like as
+ * field of TokensListStateHolder.
*
[REDACTED_AUTHOR]
*/
+@Immutable
sealed interface TokenItemState {
/** Token id */
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt
index e083f7991b..101cdc8f52 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListStateHolder.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.tokens.impl.presentation.states
+import androidx.compose.runtime.Immutable
import androidx.paging.LoadState
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
@@ -9,6 +10,7 @@ import kotlinx.coroutines.flow.Flow
*
[REDACTED_AUTHOR]
*/
+@Immutable
internal sealed interface TokensListStateHolder {
/** Toolbar state */
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt
index b4104f692b..a269659991 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/states/TokensListToolbarState.kt
@@ -1,6 +1,13 @@
package com.tangem.tap.features.tokens.impl.presentation.states
-/** Toolbar state */
+import androidx.compose.runtime.Immutable
+
+/**
+ * Toolbar state.
+ * All subclasses is stable, but @Immutable annotation is required to use this sealed class like as
+ * field of TokensListStateHolder.
+ */
+@Immutable
sealed interface TokensListToolbarState {
/** Callback to be invoked when BackButton is being clicked */
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt
index d378d6ba75..44e620b8ac 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokensListScreen.kt
@@ -56,12 +56,13 @@ import kotlinx.coroutines.flow.flowOf
[REDACTED_AUTHOR]
*/
@Composable
-internal fun TokensListScreen(stateHolder: TokensListStateHolder) {
+internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modifier = Modifier) {
BackHandler(onBack = stateHolder.toolbarState.onBackButtonClick)
var floatingButtonHeight by remember { mutableStateOf(value = 0.dp) }
Scaffold(
+ modifier = modifier,
topBar = { TokensListToolbar(state = stateHolder.toolbarState) },
floatingActionButton = {
if (stateHolder is TokensListStateHolder.ManageContent) {
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 607d8c63c4..d86a41c389 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
@@ -9,7 +9,7 @@ import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken
import com.tangem.tap.common.extensions.addContext
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
-import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
@@ -114,7 +114,8 @@ class MultiWalletMiddleware {
)
}
.doOnSuccess { updatedUserWallet ->
- store.dispatchOnMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList()))
+ store.dispatchWithMain(WalletAction.MultiWallet.AddMissingDerivations(emptyList()))
+ store.dispatchWithMain(GlobalAction.SaveScanResponse(updatedUserWallet.scanResponse))
store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true)
}
}
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 c7bde94141..d7a0da7703 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
@@ -17,6 +17,7 @@ import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
+import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapError
@@ -97,12 +98,12 @@ class WalletMiddleware {
Timber.e("Unable to create wallet, no user wallet selected")
return@launch
}
+ val updatedScanResponse = selectedUserWallet.scanResponse.copy(
+ card = result.data,
+ )
+ store.dispatchWithMain(GlobalAction.SaveScanResponse(updatedScanResponse))
userWalletsListManager.update(selectedUserWallet.walletId) { userWallet ->
- userWallet.copy(
- scanResponse = userWallet.scanResponse.copy(
- card = result.data,
- ),
- )
+ userWallet.copy(scanResponse = updatedScanResponse)
}
}
is CompletionResult.Failure -> Unit
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 3bd06aed6a..e82c2f9547 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,24 +1,48 @@
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) {
- AddressType.Legacy -> R.id.chip_legacy
- AddressType.Default -> R.id.chip_default
+ is BitcoinAddressType.Legacy -> R.id.chip_legacy
+ is BitcoinAddressType.Segwit -> R.id.chip_default
+ is CardanoAddressType.Byron -> R.id.chip_legacy
+ is CardanoAddressType.Shelley -> R.id.chip_default
+ else -> View.NO_ID
}
}
fun idToType(id: Int, blockchain: Blockchain?): AddressType? {
return when (id) {
- R.id.chip_default -> AddressType.Default
- R.id.chip_legacy -> AddressType.Legacy
+ R.id.chip_default -> {
+ when (blockchain) {
+ Blockchain.Bitcoin,
+ Blockchain.BitcoinTestnet,
+ Blockchain.Litecoin,
+ Blockchain.BitcoinCash,
+ -> BitcoinAddressType.Segwit
+ Blockchain.CardanoShelley -> CardanoAddressType.Shelley
+ else -> null
+ }
+ }
+ R.id.chip_legacy -> {
+ when (blockchain) {
+ Blockchain.Bitcoin,
+ Blockchain.BitcoinTestnet,
+ Blockchain.Litecoin,
+ Blockchain.BitcoinCash,
+ -> BitcoinAddressType.Legacy
+ Blockchain.CardanoShelley -> CardanoAddressType.Byron
+ else -> null
+ }
+ }
else -> null
}
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt
index f40f0d38dc..f2d65a4fcf 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/utils/WalletDataOperations.kt
@@ -12,6 +12,7 @@ import com.tangem.tap.common.extensions.toFormattedCurrencyString
import com.tangem.tap.common.extensions.toFormattedFiatValue
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
+import com.tangem.tap.features.wallet.models.PendingTransactionType
import com.tangem.tap.features.wallet.models.WalletWarning
import com.tangem.tap.features.wallet.redux.WalletMainButton
import com.tangem.tap.features.wallet.redux.utils.UNKNOWN_AMOUNT_SIGN
@@ -19,9 +20,24 @@ import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import java.math.BigDecimal
internal fun WalletDataModel.mainButton(blockchainAmount: BigDecimal): WalletMainButton = WalletMainButton.SendButton(
- enabled = !isEmptyAmount && status.pendingTransactions.isEmpty() && !blockchainAmount.isZero(),
+ enabled = !isEmptyAmount &&
+ hasPendingTransactions() &&
+ !blockchainAmount.isZero(),
)
+internal fun WalletDataModel.hasPendingTransactions(): Boolean {
+ // for now check pending ongoing only just for BTC, later test and add other utxo networks
+ val isBitcoinBlockchain =
+ currency.blockchain == Blockchain.Bitcoin || currency.blockchain == Blockchain.BitcoinTestnet
+ if (currency.isBlockchain() && isBitcoinBlockchain) {
+ val outgoingTransactions = status.pendingTransactions.filter {
+ it.type == PendingTransactionType.Outgoing
+ }
+ return outgoingTransactions.isEmpty()
+ }
+ return status.pendingTransactions.isEmpty()
+}
+
internal fun WalletDataModel.getFormattedAmount(): String {
return status.amount.toFormattedCurrencyString(
decimals = currency.decimals,
@@ -82,7 +98,7 @@ internal fun WalletDataModel.getAvailableActions(
internal fun WalletDataModel.shouldShowMultipleAddress(): Boolean {
val listOfAddresses = walletAddresses?.list.orEmpty()
- return listOfAddresses.size > 1
+ return listOfAddresses.size > 1 && currency.blockchain != Blockchain.BitcoinCash
}
internal fun WalletDataModel.assembleWarnings(
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/TotalBalanceCard.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/TotalBalanceCard.kt
index afd1ba8c88..d89d245766 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/view/TotalBalanceCard.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/view/TotalBalanceCard.kt
@@ -4,14 +4,7 @@ import android.content.Context
import android.util.AttributeSet
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.*
import androidx.compose.material.Divider
import androidx.compose.material.Surface
import androidx.compose.material.Text
@@ -27,11 +20,7 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
-import com.tangem.core.ui.components.SelectorButton
-import com.tangem.core.ui.components.SpacerH12
-import com.tangem.core.ui.components.SpacerH4
-import com.tangem.core.ui.components.SpacerW16
-import com.tangem.core.ui.components.SpacerW4
+import com.tangem.core.ui.components.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.domain.model.TotalFiatBalance
@@ -42,7 +31,8 @@ import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.text.NumberFormat
-import java.util.*
+import java.util.Currency
+import java.util.Locale
internal class TotalBalanceCard @JvmOverloads constructor(
context: Context,
@@ -125,7 +115,7 @@ private fun TotalBalanceCardContent(state: TotalBalanceCardState, modifier: Modi
-> LoadedAmount(
amount = buildAmountString(
amount = state.amount,
- fiatCurrencySymbol = state.fiatCurrency.symbol,
+ fiatCurrency = state.fiatCurrency,
),
)
}
@@ -228,30 +218,45 @@ private fun LoadedAmount(amount: AnnotatedString, modifier: Modifier = Modifier)
}
@Composable
-private fun buildAmountString(amount: BigDecimal?, fiatCurrencySymbol: String): AnnotatedString {
+private fun buildAmountString(amount: BigDecimal?, fiatCurrency: FiatCurrency): AnnotatedString {
if (amount == null) return AnnotatedString(text = UNKNOWN_AMOUNT_SIGN)
- val formatter = NumberFormat.getInstance(Locale.getDefault()) as? DecimalFormat
- ?: return AnnotatedString("${amount.toPlainString()} $fiatCurrencySymbol")
- val decimalFormat = formatter.apply {
- maximumFractionDigits = 2
- minimumFractionDigits = 2
- isGroupingUsed = true
- this.roundingMode = RoundingMode.HALF_UP
+ val locale = Locale.getDefault()
+ val fractionDigits = 2
+ val formatter = NumberFormat.getCurrencyInstance(locale) as? DecimalFormat
+ ?: return AnnotatedString("${amount.toPlainString()} ${fiatCurrency.symbol}")
+
+ val currencyToShow = "${fiatCurrency.symbol} "
+ val scaledAmount = Currency.getInstance(fiatCurrency.code)?.let { currency ->
+ formatter.currency = currency
+ formatter.maximumFractionDigits = fractionDigits
+ formatter.minimumFractionDigits = fractionDigits
+ formatter.isGroupingUsed = true
+ formatter.roundingMode = RoundingMode.HALF_UP
+ formatter.format(amount).replace(currency.symbol, currencyToShow)
+ } ?: formatter.format(amount)
+
+ val integer = scaledAmount.substringBefore(formatter.decimalFormatSymbols.decimalSeparator)
+ var reminder = scaledAmount.substringAfter(formatter.decimalFormatSymbols.decimalSeparator)
+
+ // if locale formatted currency at the end, remember it and place out of AnnotatedString
+ val currency = if (reminder.endsWith(currencyToShow)) {
+ reminder = reminder.dropLast(currencyToShow.length)
+ currencyToShow
+ } else {
+ ""
}
- val scaledAmount = decimalFormat.format(amount)
- val integer = scaledAmount.substringBefore(decimalFormat.decimalFormatSymbols.decimalSeparator)
- val reminder = scaledAmount.substringAfter(decimalFormat.decimalFormatSymbols.decimalSeparator)
return buildAnnotatedString {
append(integer)
- append(decimalFormat.decimalFormatSymbols.decimalSeparator)
+ append(formatter.decimalFormatSymbols.decimalSeparator)
append(
AnnotatedString(
- text = "$reminder $fiatCurrencySymbol",
+ text = reminder,
spanStyle = TangemTheme.typography.h3.toSpanStyle(),
),
)
+ append(currency) // it is not empty if was placed at the end after locale formatting
}
}
diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt
deleted file mode 100644
index d9d00a663b..0000000000
--- a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyApi.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.tangem.tap.network.payid
-
-import com.squareup.moshi.JsonClass
-import retrofit2.http.GET
-import retrofit2.http.Header
-import retrofit2.http.Path
-
-/**
-[REDACTED_AUTHOR]
- */
-interface PayIdVerifyApi {
- @GET("{user}")
- suspend fun verifyAddress(
- @Path("user") user: String,
- @Header("Accept") acceptNetworkHeader: String,
- @Header("PayID-Version") payIdVersion: String = "1.0",
- ): VerifyPayIdResponse
-}
-
-@JsonClass(generateAdapter = true)
-data class VerifyPayIdResponse(
- val addresses: List = mutableListOf(),
- val payId: String? = null,
-) {
- fun getAddressDetails(): PayIdAddressDetails? = if (addresses.isNotEmpty()) addresses[0].addressDetails else null
-}
-
-@JsonClass(generateAdapter = true)
-data class PayIdAddress(
- var paymentNetwork: String,
- var environment: String,
- var addressDetailsType: String,
- var addressDetails: PayIdAddressDetails,
-)
-
-@JsonClass(generateAdapter = true)
-data class PayIdAddressDetails(
- var address: String,
- var tag: String? = null,
-)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt b/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt
deleted file mode 100644
index 05d16f8724..0000000000
--- a/app/src/main/java/com/tangem/tap/network/payid/PayIdVerifyService.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.tangem.tap.network.payid
-
-import com.tangem.common.services.Result
-import com.tangem.common.services.performRequest
-import com.tangem.datasource.api.common.createRetrofitInstance
-
-/**
-[REDACTED_AUTHOR]
- */
-class PayIdVerifyService(
- private val baseUrl: String,
-) {
-
- private val api = createRetrofitInstance(
- baseUrl = baseUrl,
- logEnabled = false,
- ).create(PayIdVerifyApi::class.java)
-
- suspend fun verifyAddress(user: String, network: String): Result {
- return performRequest { api.verifyAddress(user, createNetworkHeader(network)) }
- }
-
- private fun createNetworkHeader(network: String): String = "application/$network-mainnet+json"
-}
\ No newline at end of file
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 aab3cfd808..35c2d83718 100644
--- a/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt
+++ b/app/src/main/java/com/tangem/tap/proxy/AppStateHolder.kt
@@ -19,6 +19,7 @@ import javax.inject.Inject
*/
class AppStateHolder @Inject constructor() {
+ @Deprecated("Use scan response from selected user wallet")
var scanResponse: ScanResponse? = null
var walletState: WalletState? = null
var userTokensRepository: UserTokensRepository? = null
diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml
index fe0eebf2a3..8ec3dfb594 100644
--- a/app/src/main/res/layout/fragment_send.xml
+++ b/app/src/main/res/layout/fragment_send.xml
@@ -39,8 +39,8 @@
android:orientation="vertical">
diff --git a/app/src/main/res/layout/fragment_shop.xml b/app/src/main/res/layout/fragment_shop.xml
index 523b68c535..0a61bf5b4a 100644
--- a/app/src/main/res/layout/fragment_shop.xml
+++ b/app/src/main/res/layout/fragment_shop.xml
@@ -271,7 +271,7 @@
android:layout_marginTop="14dp"
android:background="@drawable/shape_rectangle_rounded_4"
android:padding="16dp"
- android:text="@string/shop_sold_out_description_prefix"
+ android:text="@string/shop_sold_out_description"
android:textColor="@color/text_tertiary"
android:textSize="16sp" />
diff --git a/app/src/main/res/layout/layout_address.xml b/app/src/main/res/layout/layout_address.xml
index 22853700ed..713c384939 100644
--- a/app/src/main/res/layout/layout_address.xml
+++ b/app/src/main/res/layout/layout_address.xml
@@ -150,63 +150,6 @@
app:layout_constraintTop_toBottomOf="@id/tv_explore"
tools:text="Send only Ethereum (ETH) from Ethereum network to this address. Using other tokens and networks may result in loss of funds." />
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/layout_send_address_payid.xml b/app/src/main/res/layout/layout_send_address.xml
similarity index 84%
rename from app/src/main/res/layout/layout_send_address_payid.xml
rename to app/src/main/res/layout/layout_send_address.xml
index 1bf69a649f..c6dd2873c8 100644
--- a/app/src/main/res/layout/layout_send_address_payid.xml
+++ b/app/src/main/res/layout/layout_send_address.xml
@@ -18,10 +18,9 @@
app:layout_constraintTop_toTopOf="parent">
+ app:layout_constraintTop_toTopOf="@+id/tilAddress">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ app:layout_constraintTop_toBottomOf="@+id/tilAddress">
+
+ false
+
+
diff --git a/app/src/main/res/values/bool.xml b/app/src/main/res/values/bool.xml
new file mode 100644
index 0000000000..d5677354f0
--- /dev/null
+++ b/app/src/main/res/values/bool.xml
@@ -0,0 +1,5 @@
+
+
+ true
+
+
diff --git a/app/src/tangemAccess/java/com/tangem/Test2.java b/app/src/tangemAccess/java/com/tangem/Test2.java
deleted file mode 100644
index c4c336af47..0000000000
--- a/app/src/tangemAccess/java/com/tangem/Test2.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.tangem;
-
-public class Test2 {
-}
diff --git a/app/src/tangemAccess/java/com/tangem/ui/ConfirmTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/ConfirmTransactionFragment.kt
deleted file mode 100644
index f934c827fc..0000000000
--- a/app/src/tangemAccess/java/com/tangem/ui/ConfirmTransactionFragment.kt
+++ /dev/null
@@ -1,305 +0,0 @@
-package com.tangem.ui
-
-import android.app.Activity
-import android.content.SharedPreferences
-import android.nfc.NfcAdapter
-import android.nfc.Tag
-import android.os.Build
-import android.os.Bundle
-import android.preference.PreferenceManager
-import android.text.Editable
-import android.text.Html
-import android.text.TextWatcher
-import android.util.Log
-import android.view.View
-import android.widget.Toast
-import androidx.activity.OnBackPressedCallback
-import androidx.core.os.bundleOf
-import com.tangem.Constant
-import com.tangem.data.Blockchain
-import com.tangem.tangem_card.data.TangemCard
-import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
-import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
-import com.tangem.tangem_sdk.data.loadFromBundle
-import com.tangem.ui.activity.MainActivity
-import com.tangem.ui.fragment.BaseFragment
-import com.tangem.ui.fragment.pin.PinRequestFragment
-import com.tangem.ui.navigation.NavigationResultListener
-import com.tangem.util.UtilHelper
-import com.tangem.wallet.CoinEngine
-import com.tangem.wallet.CoinEngineFactory
-import com.tangem.wallet.R
-import com.tangem.wallet.TangemContext
-import kotlinx.android.synthetic.tangemAccess.fragment_confirm_transaction.*
-import java.io.IOException
-import java.util.*
-
-class ConfirmTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback {
-
- override val layoutId = R.layout.fragment_confirm_transaction
-
- private lateinit var sp: SharedPreferences
- private lateinit var ctx: TangemContext
- private lateinit var amount: CoinEngine.Amount
-
- private var isIncludeFee: Boolean = true
- private var requestPIN2Count = 0
- private var nodeCheck = true
- private var dtVerified: Date? = null
-
- private var blockchainCallbacks: CoinEngine.BlockchainRequestsCallbacks? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- sp = PreferenceManager.getDefaultSharedPreferences(context)
- ctx = TangemContext.loadFromBundle(requireContext(), arguments)
-
- val callback = object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- navigateUp()
- }
- }
- requireActivity().onBackPressedDispatcher.addCallback(this, callback)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- val engine = CoinEngineFactory.create(ctx)
-
- @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
- Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
- else
- Html.fromHtml(engine!!.balanceHTML)
- tvBalance.text = html
-
- isIncludeFee = arguments?.getBoolean(Constant.EXTRA_FEE_INCLUDED, true) ?: true
-
- if (isIncludeFee)
- tvIncFee.setText(R.string.confirm_transaction_including_fee)
- else
- tvIncFee.setText(R.string.confirm_transaction_not_including_fee)
-
- amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT) ?: "0",
- arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY) ?: "")
-
- if (engine.allowSelectFeeInclusion())
- tvIncFee.visibility = View.VISIBLE
- else
- tvIncFee.visibility = View.INVISIBLE
-
- if (ctx.card.blockchainID == Blockchain.Token.id) {
- // for Blockchain.Token limit decimals
- etAmount.setText(amount.toValueString(ctx.card.tokensDecimal))
- } else {
- // for others
- etAmount.setText(amount.toValueString())
- }
-
- tvCurrency.text = engine.balanceCurrency
- tvCurrency2.text = engine.feeCurrency
- tvCardID.text = ctx.card.cidDescription
- etWallet.setText(arguments?.getString(Constant.EXTRA_TARGET_ADDRESS))
-
- btnSend.visibility = View.INVISIBLE
-
- if (!engine.allowSelectFeeLevel()) {
- rgFee.visibility = View.INVISIBLE
- }
-
- etFee.isEnabled = sp.getBoolean(getString(R.string.pref_manual_editing_fee), false)
-
- // set listeners
- rgFee.setOnCheckedChangeListener { _, checkedId -> doSetFee(checkedId) }
- etFee.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
-
- }
-
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
- try {
- val eqFee = engine.evaluateFeeEquivalent(etFee!!.text.toString())
- tvFeeEquivalent.text = eqFee
-
- if (!ctx.coinData!!.amountEquivalentDescriptionAvailable) {
- tvFeeEquivalent.error = getString(R.string.confirm_transaction_error_service_unavailable)
- tvCurrency2.visibility = View.GONE
- tvFeeEquivalent.visibility = View.GONE
- } else
- tvFeeEquivalent.error = null
-
- if (sp.getBoolean(getString(R.string.pref_manual_editing_fee), false))
- (activity as MainActivity).toastHelper
- .showSingleToast(context, getString(R.string.confirm_transaction_warning_risk_delaying))
-
- } catch (e: Exception) {
- e.printStackTrace()
- tvFeeEquivalent.text = ""
- }
- }
-
- override fun afterTextChanged(s: Editable) {
-
- }
- })
- btnSend.setOnClickListener {
- if (UtilHelper.isOnline(requireContext())) {
- val calendar = Calendar.getInstance()
- calendar.add(Calendar.MINUTE, -1)
-
- if (dtVerified == null || dtVerified!!.before(calendar.time)) {
- finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_data_is_outdated))
- return@setOnClickListener
- }
-
- val engineCoin = CoinEngineFactory.create(ctx)
-
- if (engineCoin!!.isNeedCheckNode && !nodeCheck) {
- Toast.makeText(context, getString(R.string.confirm_transaction_error_cannot_reach_node), Toast.LENGTH_LONG).show()
- return@setOnClickListener
- }
-
- val txFee = engineCoin.convertToAmount(etFee.text.toString(), tvCurrency2.text.toString())
- val txAmount = engineCoin.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString())
-
- if (!engineCoin.hasBalanceInfo()) {
- finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_cannot_check_balance))
- return@setOnClickListener
-
- } else if (!engineCoin.isBalanceNotZero) {
- finishWithError(Activity.RESULT_CANCELED, getString(R.string.general_wallet_empty))
- return@setOnClickListener
-
- } else if (!engineCoin.isExtractPossible) {
- finishWithError(Activity.RESULT_CANCELED, getString(R.string.confirm_transaction_error_incoming_transaction_unconfirmed))
- return@setOnClickListener
- }
-
- if (!engineCoin.checkNewTransactionAmountAndFee(txAmount, txFee, isIncludeFee)) {
- finishWithError(Activity.RESULT_CANCELED, getString(R.string.prepare_transaction_error_not_enough_funds))
- return@setOnClickListener
- }
-
- requestPIN2Count = 0
- val data = Bundle()
- data.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString())
- ctx.saveToBundle(data)
- data.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
- navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_, R.id.action_confirmTransactionFragment_to_pinRequestFragment, data)
- } else
- Toast.makeText(context, getString(R.string.general_error_no_connection), Toast.LENGTH_SHORT).show()
- }
-
- progressBar.visibility = View.VISIBLE
-
- if (!navigatedBack) requestFee()
- }
-
- private fun requestFee() {
- val coinEngine = CoinEngineFactory.create(ctx)
- coinEngine!!.requestFee(
- object : CoinEngine.BlockchainRequestsCallbacks {
- override fun onComplete(success: Boolean) {
- if (success) {
- progressBar?.visibility = View.INVISIBLE
- dtVerified = Date()
- doSetFee(rgFee?.checkedRadioButtonId ?: R.id.rbNormalFee)
- } else {
- finishWithError(Activity.RESULT_CANCELED, ctx.error)
- }
- }
-
- override fun onProgress() {
- }
-
- override fun allowAdvance(): Boolean {
- return UtilHelper.isOnline(requireContext())
- }
- },
- etWallet.text.toString(),
- amount)
- }
-
- override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
- Log.d("LIFECYCLE", "NavigationResult assessed ${this::class.java.simpleName}")
- if (requestCode == Constant.REQUEST_CODE_SIGN_TRANSACTION) {
- if (data != null) {
- if (data.containsKey(EXTRA_TANGEM_CARD_UID) && data.containsKey(EXTRA_TANGEM_CARD)) {
- val updatedCard = TangemCard(data.getString(EXTRA_TANGEM_CARD_UID))
- updatedCard.loadFromBundle(data.getBundle(EXTRA_TANGEM_CARD))
- ctx.card = updatedCard
- }
- }
- if (resultCode == Constant.RESULT_INVALID_PIN_ && requestPIN2Count < 2) {
- requestPIN2Count++
- val bundle = Bundle()
- bundle.putString(Constant.EXTRA_MODE, PinRequestFragment.Mode.RequestPIN2.toString())
- ctx.saveToBundle(bundle)
- bundle.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
- navigateForResult(Constant.REQUEST_CODE_REQUEST_PIN2_,
- R.id.action_confirmTransactionFragment_to_pinRequestFragment,
- bundle)
- return
- }
- navigateBackWithResult(resultCode, data)
- } else if (requestCode == Constant.REQUEST_CODE_REQUEST_PIN2_) {
- if (resultCode == Activity.RESULT_OK) {
- val bundle = Bundle()
- ctx.saveToBundle(bundle)
- bundle.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
- bundle.putString(Constant.EXTRA_AMOUNT, etAmount.text.toString())
- bundle.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
- bundle.putString(Constant.EXTRA_FEE, etFee.text.toString())
- bundle.putString(Constant.EXTRA_FEE_CURRENCY, tvCurrency2.text.toString())
- bundle.putBoolean(Constant.EXTRA_FEE_INCLUDED, isIncludeFee)
- navigateForResult(Constant.REQUEST_CODE_SIGN_TRANSACTION,
- R.id.action_confirmTransactionFragment_to_signTransactionFragment,
- bundle)
- } else
- Toast.makeText(context, R.string.confirm_transaction_error_pin_2_is_required, Toast.LENGTH_LONG).show()
- }
- }
-
- override fun onTagDiscovered(tag: Tag) {
- try {
- (activity as MainActivity).nfcManager.ignoreTag(tag)
- } catch (e: IOException) {
- e.printStackTrace()
- }
- }
-
- private fun doSetFee(checkedRadioButtonId: Int) {
- var txtFee = ""
- when (checkedRadioButtonId) {
- R.id.rbMinimalFee ->
- if (ctx.coinData.minFee != null) {
- txtFee = ctx.coinData.minFee!!.toValueString()
- btnSend?.visibility = View.VISIBLE
- } else
- btnSend?.visibility = View.INVISIBLE
-
- R.id.rbNormalFee ->
- if (ctx.coinData.normalFee != null) {
- txtFee = ctx.coinData.normalFee!!.toValueString()
- btnSend?.visibility = View.VISIBLE
- } else
- btnSend?.visibility = View.INVISIBLE
-
- R.id.rbMaximumFee ->
- if (ctx.coinData.maxFee != null) {
- txtFee = ctx.coinData.maxFee!!.toValueString()
- btnSend?.visibility = View.VISIBLE
- } else
- btnSend?.visibility = View.INVISIBLE
- }
- etFee?.setText(txtFee.replace(',', '.'))
- }
-
- private fun finishWithError(errorCode: Int, message: String) {
- navigateBackWithResult(
- errorCode,
- bundleOf(Constant.EXTRA_MESSAGE to message),
- R.id.loadedWalletFragment)
- }
-}
\ No newline at end of file
diff --git a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt
deleted file mode 100644
index 387f992bb6..0000000000
--- a/app/src/tangemAccess/java/com/tangem/ui/PrepareTransactionFragment.kt
+++ /dev/null
@@ -1,182 +0,0 @@
-package com.tangem.ui
-
-import android.app.Activity
-import android.content.Context
-import android.nfc.NfcAdapter
-import android.nfc.Tag
-import android.os.Build
-import android.os.Bundle
-import android.text.Html
-import android.view.View
-import android.view.inputmethod.EditorInfo
-import android.view.inputmethod.InputMethodManager
-import android.widget.Toast
-import com.tangem.Constant
-import com.tangem.data.isPayIdSupported
-import com.tangem.ui.activity.MainActivity
-import com.tangem.ui.fragment.BaseFragment
-import com.tangem.ui.fragment.qr.CameraPermissionManager
-import com.tangem.ui.navigation.NavigationResultListener
-import com.tangem.util.UtilHelper
-import com.tangem.util.extensions.isStart2CoinCard
-import com.tangem.wallet.CoinEngineFactory
-import com.tangem.wallet.R
-import com.tangem.wallet.TangemContext
-import kotlinx.android.synthetic.tangemAccess.fragment_prepare_transaction.*
-import java.io.IOException
-
-class PrepareTransactionFragment : BaseFragment(), NavigationResultListener, NfcAdapter.ReaderCallback {
- companion object {
- val TAG: String = PrepareTransactionFragment::class.java.simpleName
- }
-
- override val layoutId = R.layout.fragment_prepare_transaction
-
- private val ctx: TangemContext by lazy { TangemContext.loadFromBundle(context, arguments) }
- private val cameraPermissionManager: CameraPermissionManager by lazy { CameraPermissionManager(this) }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- tvCardID.text = ctx.card?.cidDescription
- val engine = CoinEngineFactory.create(ctx)
-
- @Suppress("DEPRECATION") val html = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
- Html.fromHtml(engine!!.balanceHTML, Html.FROM_HTML_MODE_LEGACY)
- else
- Html.fromHtml(engine!!.balanceHTML)
- tvBalance.text = html
-
- if (ctx.blockchain.isPayIdSupported() && !ctx.card.isStart2CoinCard()) {
- etWallet.hint = getString(R.string.prepare_transaction_hint_address_or_pay_id)
- }
-
- if (!engine.allowSelectFeeInclusion()) {
- rgIncFee.visibility = View.INVISIBLE
- } else {
- rgIncFee.visibility = View.VISIBLE
- }
-
- if (ctx.card!!.remainingSignatures < 2) {
- etAmount.isEnabled = false
- }
-
- if (ctx.card.remainingSignatures == 1) {
- androidx.appcompat.app.AlertDialog.Builder(requireContext())
- .setTitle(R.string.prepare_transaction_warning_last_signature)
- .setMessage(R.string.prepare_transaction_warning_send_full_amount)
- .setPositiveButton(R.string.general_ok) { _, _ -> }
- .create()
- .show()
- }
-
- tvCurrency.text = engine.balance.currency
- etAmount.setText(engine.balance.toValueString())
-
- // limit number of symbols after comma
- etAmount.filters = engine.amountInputFilters
-
- // set listeners
- etAmount.setOnEditorActionListener { lv, actionId, _ ->
- if (actionId == EditorInfo.IME_ACTION_DONE) {
- val imm = lv.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
- imm.hideSoftInputFromWindow(lv.windowToken, 0)
- lv.clearFocus()
- true
- } else {
- false
- }
- }
-
- btnVerify.setOnClickListener {
- if (!UtilHelper.isOnline(requireContext())) {
- Toast.makeText(context, R.string.general_error_no_connection, Toast.LENGTH_LONG).show()
- return@setOnClickListener
- }
-
- val engine1 = CoinEngineFactory.create(ctx)
- val strAmount: String = etAmount.text.toString().replace(",", ".")
- val amount = engine1!!.convertToAmount(etAmount.text.toString(), tvCurrency.text.toString())
-
- try {
- if (!engine.checkNewTransactionAmount(amount))
- etAmount.error = getString(R.string.prepare_transaction_error_not_enough_funds)
- else
- etAmount.error = null
- } catch (e: Exception) {
- etAmount.error = getString(R.string.prepare_transaction_error_unknown_amount_format)
- }
-
- // check wallet address
- if (!engine1.validateAddress(etWallet.text.toString())) {
- etWallet.error = getString(R.string.prepare_transaction_error_incorrect_destination)
- return@setOnClickListener
- } else
- etWallet.error = null
-
- if (etWallet.text.toString() == ctx.coinData!!.wallet) {
- etWallet.error = getString(R.string.prepare_transaction_error_same_address)
- return@setOnClickListener
- }
-
- if (!etAmount.error.isNullOrEmpty() || !etWallet.error.isNullOrEmpty()) {
- return@setOnClickListener
- }
-
- val data = Bundle()
- ctx.saveToBundle(data)
- data.putString(Constant.EXTRA_TARGET_ADDRESS, etWallet!!.text.toString())
- data.putBoolean(Constant.EXTRA_FEE_INCLUDED, (rgIncFee!!.checkedRadioButtonId == R.id.rbFeeIn))
- data.putString(Constant.EXTRA_AMOUNT, strAmount)
- data.putString(Constant.EXTRA_AMOUNT_CURRENCY, tvCurrency.text.toString())
- navigateForResult(
- Constant.REQUEST_CODE_SEND_TRANSACTION__,
- R.id.action_prepareTransactionFragment_to_confirmTransactionFragment,
- data)
- }
-
- ivCamera.setOnClickListener {
- if (cameraPermissionManager.isPermissionGranted()) {
- navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment)
- } else {
- cameraPermissionManager.requirePermission()
- }
- }
- }
-
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- cameraPermissionManager.handleRequestPermissionResult(requestCode, grantResults) {
- navigateForResult(Constant.REQUEST_CODE_SCAN_QR, R.id.action_prepareTransactionFragment_to_qrScanFragment)
- }
- }
-
- override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
- if (requestCode == Constant.REQUEST_CODE_SCAN_QR && resultCode == Activity.RESULT_OK && data != null && data.containsKey("QRCode")) {
- val code = data.getString("QRCode")
- val schemeSplit = code!!.split(":")
- when (schemeSplit.size) {
- 2 -> {
- if (schemeSplit[0] == ctx.blockchain.uriScheme) {
- etWallet?.setText(schemeSplit[1])
- } else {
- etWallet?.setText(code)
- }
- }
- else -> {
- etWallet?.setText(code)
- }
- }
- } else if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION__) {
- navigateBackWithResult(resultCode, data)
- }
- }
-
- override fun onTagDiscovered(tag: Tag) {
- try {
- (activity as MainActivity).nfcManager.ignoreTag(tag)
- } catch (e: IOException) {
- e.printStackTrace()
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt b/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt
deleted file mode 100644
index fdcdc64a72..0000000000
--- a/app/src/tangemAccess/java/com/tangem/ui/SignTransactionFragment.kt
+++ /dev/null
@@ -1,312 +0,0 @@
-package com.tangem.ui
-
-import android.app.Activity
-import android.content.res.ColorStateList
-import android.graphics.Color
-import android.media.MediaPlayer
-import android.nfc.NfcAdapter
-import android.nfc.Tag
-import android.nfc.tech.IsoDep
-import android.os.Bundle
-import android.view.View
-import androidx.activity.OnBackPressedCallback
-import com.google.firebase.analytics.FirebaseAnalytics
-import com.google.firebase.crashlytics.FirebaseCrashlytics
-import com.tangem.App
-import com.tangem.Constant
-import com.tangem.tangem_card.reader.CardProtocol
-import com.tangem.tangem_card.tasks.SignTask
-import com.tangem.tangem_card.util.Util
-import com.tangem.tangem_sdk.android.nfc.NfcDeviceAntennaLocation
-import com.tangem.tangem_sdk.android.reader.NfcReader
-import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD
-import com.tangem.tangem_sdk.data.EXTRA_TANGEM_CARD_UID
-import com.tangem.tangem_sdk.data.asBundle
-import com.tangem.ui.activity.MainActivity
-import com.tangem.ui.dialog.NoExtendedLengthSupportDialog
-import com.tangem.ui.dialog.WaitSecurityDelayDialog
-import com.tangem.ui.fragment.BaseFragment
-import com.tangem.ui.navigation.NavigationResultListener
-import com.tangem.util.Analytics
-import com.tangem.util.AnalyticsEvent
-import com.tangem.util.LOG
-import com.tangem.wallet.CoinEngine
-import com.tangem.wallet.CoinEngineFactory
-import com.tangem.wallet.R
-import com.tangem.wallet.TangemContext
-import kotlinx.android.synthetic.main.layout_progress_horizontal.*
-import kotlinx.android.synthetic.main.layout_touch_card.*
-import kotlinx.android.synthetic.tangemAccess.fragment_sign_transaction.*
-
-
-class SignTransactionFragment : BaseFragment(), NavigationResultListener,
- NfcAdapter.ReaderCallback, CardProtocol.Notifications {
-
- companion object {
- val TAG: String = SignTransactionFragment::class.java.simpleName
- }
-
- override val layoutId = R.layout.fragment_sign_transaction
-
- private lateinit var ctx: TangemContext
- private lateinit var mpFinishSignSound: MediaPlayer
-
- private lateinit var nfcDeviceAntenna: NfcDeviceAntennaLocation
-
- private var signTransactionTask: SignTask? = null
-
- private lateinit var amount: CoinEngine.Amount
- private lateinit var fee: CoinEngine.Amount
- private var isIncludeFee = true
- private var outAddressStr: String? = null
- private var lastReadSuccess = true
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- ctx = TangemContext.loadFromBundle(context, arguments)
-
- val callback = object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- navigateBackWithResult(Activity.RESULT_CANCELED)
- }
- }
- requireActivity().onBackPressedDispatcher.addCallback(this, callback)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
-
- mpFinishSignSound = MediaPlayer.create(context, R.raw.scan_card_sound)
-
- // init NFC Antenna
- nfcDeviceAntenna = NfcDeviceAntennaLocation(requireContext(), ivHandCardHorizontal, ivHandCardVertical, llHand, llNfc)
- nfcDeviceAntenna.init()
-
- amount = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_AMOUNT), arguments?.getString(Constant.EXTRA_AMOUNT_CURRENCY))
- fee = CoinEngine.Amount(arguments?.getString(Constant.EXTRA_FEE), arguments?.getString(Constant.EXTRA_FEE_CURRENCY))
- isIncludeFee = arguments?.getBoolean(Constant.EXTRA_FEE_INCLUDED, true) ?: true
- outAddressStr = arguments?.getString(Constant.EXTRA_TARGET_ADDRESS)
-
- tvCardID.text = ctx.card!!.cidDescription
- progressBar.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
- progressBar.visibility = View.INVISIBLE
-
- FirebaseAnalytics.getInstance(requireActivity())
- .logEvent(AnalyticsEvent.READY_TO_SIGN.event, Analytics.setCardData(ctx))
- }
-
- override fun onPause() {
- signTransactionTask?.cancel(true)
- super.onPause()
- }
-
- override fun onStop() {
- signTransactionTask?.cancel(true)
- super.onStop()
- }
-
- override fun onNavigationResult(requestCode: String, resultCode: Int, data: Bundle?) {
- if (requestCode == Constant.REQUEST_CODE_SEND_TRANSACTION_) {
- navigateBackWithResult(resultCode, data)
- }
- }
-
- override fun onTagDiscovered(tag: Tag) {
- try {
- // get IsoDep handle and run cardReader thread
- val isoDep = IsoDep.get(tag)
- val uid = tag.id
- val sUID = Util.byteArrayToHexString(uid)
-
- if (sUID == ctx.card.uid) {
- if (lastReadSuccess)
- isoDep.timeout = ctx.card.pauseBeforePIN2 + 5000
- else
- isoDep.timeout = ctx.card.pauseBeforePIN2 + 65000
-
- val coinEngine = CoinEngineFactory.create(ctx)
- coinEngine?.setOnNeedSendTransaction { tx ->
- if (tx != null) {
- val data = Bundle()
- ctx.saveToBundle(data)
- data.putByteArray(Constant.EXTRA_TX, tx)
- navigateForResult(
- Constant.REQUEST_CODE_SEND_TRANSACTION_,
- R.id.action_signTransactionFragment_to_sendTransactionFragment,
- data)
- }
- }
- val transactionToSign = coinEngine?.constructTransaction(amount, fee, isIncludeFee, outAddressStr)
-
- signTransactionTask = SignTask(ctx.card, NfcReader((activity as MainActivity).nfcManager, isoDep),
- App.localStorage, App.pinStorage, this, transactionToSign)
- signTransactionTask?.start()
- } else
- (activity as MainActivity).nfcManager.ignoreTag(isoDep.tag)
-
- } catch (e: CardProtocol.TangemException_WrongAmount) {
- try {
- val data = Bundle()
- data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
- data.putString(EXTRA_TANGEM_CARD_UID, ctx.card.uid)
- data.putBundle(EXTRA_TANGEM_CARD, ctx.card.asBundle)
- navigateBackWithResult(Activity.RESULT_CANCELED, data)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- } catch(e: IllegalArgumentException) {
- val data = Bundle()
- data.putString(Constant.EXTRA_MESSAGE, e.message)
- navigateBackWithResult(Activity.RESULT_CANCELED, data, R.id.loadedWalletFragment)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
-
- override fun onReadStart(cardProtocol: CardProtocol) {
- rlProgressBar?.post { rlProgressBar.visibility = View.VISIBLE }
-
- progressBar?.post {
- progressBar?.visibility = View.VISIBLE
- progressBar?.progress = 5
- }
- }
-
- override fun onReadProgress(protocol: CardProtocol, progress: Int) {
- progressBar?.post { progressBar?.progress = progress }
- }
-
- override fun onReadFinish(cardProtocol: CardProtocol?) {
- signTransactionTask = null
- if (cardProtocol != null) {
- if (cardProtocol.error == null) {
-
- FirebaseAnalytics.getInstance(requireActivity())
- .logEvent(AnalyticsEvent.SIGNED.event, Analytics.setCardData(ctx))
-
- rlProgressBar?.post { rlProgressBar?.visibility = View.GONE }
-
- progressBar?.post {
- progressBar?.progress = 100
- progressBar?.progressTintList = ColorStateList.valueOf(Color.GREEN)
- }
-
- mpFinishSignSound.start()
- } else {
- lastReadSuccess = false
- FirebaseCrashlytics.getInstance().recordException(cardProtocol.error)
- if (cardProtocol.error.javaClass == CardProtocol.TangemException_InvalidPIN::class.java) {
- progressBar?.post {
- progressBar?.progress = 100
- progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
- }
- progressBar?.postDelayed({
- try {
- progressBar?.progress = 0
- progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
- progressBar?.visibility = View.INVISIBLE
- val data = Bundle()
- data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_cannot_sign))
- data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
- data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
- navigateBackWithResult(Constant.RESULT_INVALID_PIN_, data)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }, 500)
- } else {
- if (cardProtocol.error is CardProtocol.TangemException_WrongAmount) {
- try {
- val data = Bundle()
- data.putString(Constant.EXTRA_MESSAGE, getString(R.string.send_transaction_error_wrong_amount))
- data.putString(EXTRA_TANGEM_CARD_UID, cardProtocol.card.uid)
- data.putBundle(EXTRA_TANGEM_CARD, cardProtocol.card.asBundle)
- navigateBackWithResult(Activity.RESULT_CANCELED, data)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
- progressBar?.post {
- if (cardProtocol.error is CardProtocol.TangemException_ExtendedLengthNotSupported) {
- if (!NoExtendedLengthSupportDialog.allReadyShowed) {
- NoExtendedLengthSupportDialog.message = getText(R.string.dialog_the_nfc_adapter_length_apdu).toString() + "\n" + getText(R.string.dialog_the_nfc_adapter_length_apdu_advice).toString()
- NoExtendedLengthSupportDialog().show(requireFragmentManager(), NoExtendedLengthSupportDialog.TAG)
- }
- } else {
- (activity as? MainActivity)?.toastHelper?.showSingleToast(
- context, getString(R.string.general_notification_scan_again)
- )
- }
- progressBar?.progress = 100
- progressBar?.progressTintList = ColorStateList.valueOf(Color.RED)
- }
- }
- }
- }
-
- rlProgressBar?.postDelayed({
- try {
- rlProgressBar?.visibility = View.GONE
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }, 500)
-
- progressBar?.postDelayed({
- try {
- progressBar?.progress = 0
- progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
- progressBar?.visibility = View.INVISIBLE
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }, 500)
- }
-
- override fun onReadCancel() {
- signTransactionTask = null
-
- progressBar?.postDelayed({
- try {
- progressBar?.progress = 0
- progressBar?.progressTintList = ColorStateList.valueOf(Color.DKGRAY)
- progressBar?.visibility = View.INVISIBLE
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }, 500)
- }
-
-// private val waitSecurityDelayDialogNew = WaitSecurityDelayDialogNew()
-
- override fun onReadBeforeRequest(timeout: Int) {
- LOG.i(TAG, "onReadBeforeRequest timeout $timeout")
- activity?.let { WaitSecurityDelayDialog.onReadBeforeRequest(it, timeout) }
-
-// if (!waitSecurityDelayDialogNew.isAdded)
-// waitSecurityDelayDialogNew.show(supportFragmentManager, WaitSecurityDelayDialogNew.TAG)
-
-
-// val readBeforeRequest = ReadBeforeRequest()
-// readBeforeRequest.timeout = timeout
-// EventBus.getDefault().post(readBeforeRequest)
- }
-
- override fun onReadAfterRequest() {
- LOG.i(TAG, "onReadAfterRequest")
- activity?.let { WaitSecurityDelayDialog.onReadAfterRequest(it) }
-
-// val readAfterRequest = ReadAfterRequest()
-// EventBus.getDefault().post(readAfterRequest)
- }
-
- override fun onReadWait(msec: Int) {
- LOG.i(TAG, "onReadWait msec $msec")
- activity?.let { WaitSecurityDelayDialog.onReadWait(it, msec) }
-
-// val readWait = ReadWait()
-// readWait.msec = msec
-// EventBus.getDefault().post(readWait)
- }
-
-}
\ No newline at end of file
diff --git a/app/src/tangemAccess/res/layout/fragment_confirm_transaction.xml b/app/src/tangemAccess/res/layout/fragment_confirm_transaction.xml
deleted file mode 100644
index 6bb5f43dda..0000000000
--- a/app/src/tangemAccess/res/layout/fragment_confirm_transaction.xml
+++ /dev/null
@@ -1,343 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/tangemAccess/res/layout/fragment_prepare_transaction.xml b/app/src/tangemAccess/res/layout/fragment_prepare_transaction.xml
deleted file mode 100644
index 7f675df2a3..0000000000
--- a/app/src/tangemAccess/res/layout/fragment_prepare_transaction.xml
+++ /dev/null
@@ -1,266 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/tangemAccess/res/layout/fragment_sign_transaction.xml b/app/src/tangemAccess/res/layout/fragment_sign_transaction.xml
deleted file mode 100644
index 19125f6911..0000000000
--- a/app/src/tangemAccess/res/layout/fragment_sign_transaction.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/tangemAccess/res/values/strings.xml b/app/src/tangemAccess/res/values/strings.xml
deleted file mode 100644
index 845dc9021a..0000000000
--- a/app/src/tangemAccess/res/values/strings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
- Tangem
-
-
\ No newline at end of file
diff --git a/buildSrc/src/main/java/Dependency.kt b/buildSrc/src/main/java/Dependency.kt
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt
index 4125f7ed48..992f6d0eb5 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt
@@ -51,4 +51,7 @@ interface TangemTechApi {
@Header("card_id") cardId: String,
@Body startReferralBody: StartReferralBody,
): ReferralResponse
+
+ @GET("shops")
+ suspend fun getShopInfo(@Query(value = "name") name: String): ShopResponse
}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ShopResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ShopResponse.kt
new file mode 100644
index 0000000000..6331011641
--- /dev/null
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/ShopResponse.kt
@@ -0,0 +1,14 @@
+package com.tangem.datasource.api.tangemTech.models
+
+import com.squareup.moshi.Json
+
+/**
+ * Shop response
+ *
+ * @property isOrderingAvailable ordering availability
+ *
+[REDACTED_AUTHOR]
+ */
+data class ShopResponse(
+ @Json(name = "canOrder") val isOrderingAvailable: Boolean,
+)
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManager.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManager.kt
index e3325c77c2..5cfaed8562 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManager.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManager.kt
@@ -17,7 +17,6 @@ interface ConfigManager {
fun resetToDefault(name: String)
companion object {
- const val IS_SENDING_TO_PAY_ID_ENABLED = "isSendingToPayIdEnabled"
const val IS_CREATING_TWIN_CARDS_ALLOWED = "isCreatingTwinCardsAllowed"
const val IS_TOP_UP_ENABLED = "isTopUpEnabled"
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
index 4f95438c33..8725f6ecab 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/ConfigManagerImpl.kt
@@ -1,13 +1,7 @@
package com.tangem.datasource.config
-import com.tangem.blockchain.common.BlockchainSdkConfig
-import com.tangem.blockchain.common.BlockchairCredentials
-import com.tangem.blockchain.common.GetBlockCredentials
-import com.tangem.blockchain.common.NowNodeCredentials
-import com.tangem.blockchain.common.QuickNodeCredentials
-import com.tangem.blockchain.common.TonCenterCredentials
+import com.tangem.blockchain.common.*
import com.tangem.datasource.config.ConfigManager.Companion.IS_CREATING_TWIN_CARDS_ALLOWED
-import com.tangem.datasource.config.ConfigManager.Companion.IS_SENDING_TO_PAY_ID_ENABLED
import com.tangem.datasource.config.ConfigManager.Companion.IS_TOP_UP_ENABLED
import com.tangem.datasource.config.models.Config
import com.tangem.datasource.config.models.ConfigModel
@@ -34,7 +28,6 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
override fun turnOff(name: String) {
when (name) {
- IS_SENDING_TO_PAY_ID_ENABLED -> config = config.copy(isSendingToPayIdEnabled = false)
IS_TOP_UP_ENABLED -> config = config.copy(isTopUpEnabled = false)
IS_CREATING_TWIN_CARDS_ALLOWED -> config = config.copy(isCreatingTwinCardsAllowed = false)
}
@@ -42,13 +35,13 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
override fun resetToDefault(name: String) {
when (name) {
- IS_SENDING_TO_PAY_ID_ENABLED ->
- config =
- config.copy(isSendingToPayIdEnabled = defaultConfig.isSendingToPayIdEnabled)
- IS_TOP_UP_ENABLED -> config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
- IS_CREATING_TWIN_CARDS_ALLOWED ->
- config =
- config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
+ IS_TOP_UP_ENABLED -> {
+ config = config.copy(isTopUpEnabled = defaultConfig.isTopUpEnabled)
+ }
+ IS_CREATING_TWIN_CARDS_ALLOWED -> {
+ config = config.copy(isCreatingTwinCardsAllowed = defaultConfig.isCreatingTwinCardsAllowed)
+ }
+ else -> Unit
}
}
@@ -57,12 +50,11 @@ internal class ConfigManagerImpl @Inject constructor() : ConfigManager {
config = config.copy(
isTopUpEnabled = model.isTopUpEnabled,
- isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
)
+
defaultConfig = defaultConfig.copy(
isTopUpEnabled = model.isTopUpEnabled,
- isSendingToPayIdEnabled = model.isSendingToPayIdEnabled,
isCreatingTwinCardsAllowed = model.isCreatingTwinCardsAllowed,
)
}
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt
index 24d8735376..db096e7df4 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/Config.kt
@@ -11,7 +11,6 @@ data class Config(
val appsFlyerDevKey: String = "",
val amplitudeApiKey: String = "",
val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(),
- val isSendingToPayIdEnabled: Boolean = true,
val isTopUpEnabled: Boolean = false,
@Deprecated("Not relevant since version 3.23")
val isCreatingTwinCardsAllowed: Boolean = false,
diff --git a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt
index cd518d4c31..a3e6445c5a 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/config/models/JsonModels.kt
@@ -8,7 +8,6 @@ import com.squareup.moshi.Json
class FeatureModel(
val isTopUpEnabled: Boolean,
- val isSendingToPayIdEnabled: Boolean,
val isCreatingTwinCardsAllowed: Boolean,
)
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 174175e4e6..b0b9550686 100644
--- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
+++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json
@@ -9,7 +9,7 @@
},
{
"name": "NEW_CARD_SCANNING_ENABLED",
- "version": "4.7.0"
+ "version": "4.8.0"
},
{
"name": "REDESIGNED_WALLET_SCREEN_ENABLED",
diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index 3e71b3a98a..831402c257 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -34,11 +34,7 @@
Der Betrag enthält nicht einige Ihrer Mittel
Betrag
Adresse
- Adresse oder PayString
Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein
- PayString ist nicht registriert
- PayString-Anfrage ist fehlgeschlagen
- PayString wird von der Blockchain nicht unterstützt
Tag
Memo
inkl. Gebühr
@@ -57,7 +53,6 @@
Ungültige Adresse
Tangem Wallet
Tangem Twin
- PayString erstellen
Die Bilanz wird aufgeladen…
Die Transaktion läuft…
Verifizierte Bilanz
diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml
index 01059b36ff..4698792b07 100644
--- a/core/res/src/main/res/values-fr/strings.xml
+++ b/core/res/src/main/res/values-fr/strings.xml
@@ -34,11 +34,7 @@
Le montant n\'inclut pas certains de vos fonds
Somme
Adresse
- Adresse ou PayString
L\'adresse est la même que celle de votre portefeuille
- PayString non enregistré
- La demande de PayString a échoué
- PayString non pris en charge par la blockchain
Tag
Memo
Inclure les commissions
@@ -57,7 +53,6 @@
Adresse incorrecte
Tangem Wallet
Tangem Twin
- Créer PayString
Solde est en cours de téléchargement…
Transaction en cours…
Solde confirmé
diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml
index bc24145f51..eb75a80647 100644
--- a/core/res/src/main/res/values-it/strings.xml
+++ b/core/res/src/main/res/values-it/strings.xml
@@ -34,11 +34,7 @@
L\'importo non include alcuni dei tuoi fondi
Importo
Indirizzo
- Indirizzo o PayString
L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio
- PayString non registrato
- Richiesta PayString fallita
- PayString non supportato dalla blockchain
Tag
Memo
Includi commissione
@@ -57,7 +53,6 @@
Indirizzo non valido
Tangem Wallet
Tangem Twin
- Crea PayString
Il saldo sta per essere caricato…
Transazione in corso…
Saldo verificato
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 2baace2c4d..87f1f3e68b 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -46,6 +46,8 @@
Чат
Принять
Добавить
+ Применить
+ Одобрение
Внимание
Баланс: %s
биометрическую аутентификацию
@@ -60,6 +62,7 @@
Готово
Включить
Включено
+ Обозреватель
Нет
Ок
Основная карта
@@ -73,7 +76,10 @@
Начать
Отправить
Успешно
+ Обмен
условия участия
+ Транзакции
+ Перевод
Я понял
Да
Адрес контракта скопирован!
@@ -255,6 +261,9 @@
Восстановление кода доступа
Идентичные карты
Код доступа
+ Группировка
+ По балансу
+ Сортировка токенов
Участвовать
Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.
Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже.
@@ -308,11 +317,7 @@
Поиск валют
Сумма
Адрес
- Адрес или PayString
Адрес совпадает с адресом кошелька
- PayString не зарегистрирован
- Не удалось выполнить запрос PayString
- PayString не поддерживается блокчейном
Недопустимый Tag. Он не будет добавлен в транзакцию.
Недопустимый Memo. Он не будет добавлен в транзакцию.
Tag
@@ -336,7 +341,7 @@
У меня есть промо-код…
Tangem Wallet
Другие способы оплаты
- Из-за большого количества заказов, которые мы получаем, cроки доставки могут быть увеличены.
+ Из-за высокого количества заказов, которые мы получаем, доставка может быть задержана на срок до 5 недель в зависимости от вашего местоположения
Итого
Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату.
Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.
@@ -382,7 +387,6 @@
Безлимитно
Открыть в обозревателе
В процессе
- Обмен
Обменять
Обмен %s на
Котировки включают дополнительную комиссию Tangem в размере %s. Это помогает нам предоставлять первоклассный продукт.
@@ -411,9 +415,11 @@
- %d токенов
- %d токенов
+ контракт: %s
У вас еще нет транзакций
- Не удалось загрузить транзакции
- Транзакции
+ Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.
+ История транзакций в настоящее время не поддерживается для этого блокчейна. Но не волнуйтесь, мы работаем над этим! А пока вы можете проверить ее в обозревателе.
+ от: %s
В процессе…
Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d
Это карта, которую вы держите в руках. У парной карты номер %s.\n\nОбе карты можно использовать для вывода средств из этого кошелька.
@@ -435,7 +441,6 @@
Одновалютные
Мои кошельки
Разблокировать все с %s
- Создать PayString
История транзакций
Сеть недоступна
Блокчейн недоступен. Попробуй позже.
@@ -457,6 +462,7 @@
Невозможно отправить транзакцию. Недостаточно средств.
Не удалось установить сессию WalletConnect. Пожалуйста, повторите попытку позже.
Не удалось установить сессию WalletConnect за отведённое время. Пожалуйста, повторите попытку позже.
+ Запрос на подключение через WalletConnect содержит неподдерживаемые блокчеины. Неподдерживаемые блокчеины:\n
Cоединение с этим Dapp сервисом не может быть установлено из-за его технической реализации.
Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново.
Вставить из буфера обмена
diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml
index 08f4e9e68b..67415cab49 100644
--- a/core/res/src/main/res/values-zh-rTW/strings.xml
+++ b/core/res/src/main/res/values-zh-rTW/strings.xml
@@ -56,6 +56,7 @@
完成
允許
啟用
+ 交易
否
OK
主卡片
@@ -69,7 +70,9 @@
開始
提交
成功
+ 交換
條款和條件
+ 交易
我了解
是
已複製代幣地址
@@ -298,11 +301,7 @@
搜尋代幣
數量
地址
- 地址或 PayString
地址與錢包地址相同
- PayString 未註冊
- PayString 請求失敗
- PayString 不被區塊鏈支持
標籤無效。它不會被添加到交易中
Memo無效。 它不會被添加到交易中
Tag
@@ -367,7 +366,6 @@
要繼續,您需要允許 1inch 智能合約使用您的 %s
在瀏覽器中查看
進行中
- 交換
交易
交易 %s 至
此外,報價包括%s的 Tangem 費用。這有助於我們提供一流的產品
@@ -392,7 +390,6 @@
您還沒有任何交易
無法加載交易
- 交易
進行中…
您掃描了同一張卡片。要創建雙錢包,您需要掃描編號為 %d 的卡
這一個是你手裡拿著的,另一個是編號為 %s 的,這兩張卡都可以用來從這個錢包中提取資金
@@ -414,7 +411,6 @@
單一幣種
我的錢包
用 %s 解鎖全部
- 創建支付字符串
交易記錄
網路無法使用
區塊鍊無法使用。稍後再試
@@ -436,6 +432,7 @@
無法交易,無足夠資金
未能建立 WalletConnect 連接。請稍後再試
無法建立 WalletConnect 連接:超時錯誤。請稍後再試
+ 會話請求包含不支持 WalletConnect 連接的區塊鏈。不支持的區塊鏈:
由於技術問題,無法與此 Dapp 建立連接
沒有 %s 網路,請先加入後再試一次
從剪貼板貼上
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index ea3c2c3c93..2173d9d103 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -44,6 +44,8 @@
Support
Accept
Add
+ Apply
+ Approval
Attention
Balance: %s
biometric authentication
@@ -58,6 +60,7 @@
Done
Enable
Enabled
+ Explorer
No
OK
Primary Card
@@ -71,7 +74,10 @@
Start
Submit
Success
+ Swap
terms and conditions
+ Transactions
+ Transfer
I understand
Yes
Contract address copied!
@@ -253,6 +259,9 @@
Access code restore
Identical cards
Access code
+ Group
+ By balance
+ Organize tokens
Participate
Failed to load the information about the referral program. Please try again later.
Failed to load the information about the referral program. Error code: %s. Please try again later.
@@ -304,11 +313,7 @@
Search tokens
Amount
Address
- Address or PayString
Address is the same as wallet address
- PayString not registered
- PayString request failed
- PayString unsupported by blockchain
Invalid Tag. It won\'t be added to the transaction.
Invalid Memo. It won\'t be added to the transaction.
Tag
@@ -332,7 +337,7 @@
I have a promo code…
Tangem Wallet
Other payment methods
- Due to the high volume of orders we are receiving, Shipping and Local Delivery orders may be delayed.
+ Due to the high volume of orders we are receiving shipping may be delayed up to 5 weeks depending on your location
Total
Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free.
Store your crypto assets secure while keeping private keys contained in your card
@@ -378,7 +383,6 @@
Unlimited
View in Explorer
In progress
- Swap
Swap
Swap of %s to
Quotes include an additional Tangem commission of %s. This helps us deliver a top-of-the-line product.
@@ -403,9 +407,12 @@
- %d token
- %d tokens
+ contract: %s
You don\'t have any transactions yet
- Failed to load transactions
- Transactions
+ Failed to load transaction history.\nClick on reload button to update the information.
+ Transaction history is currently not supported for this blockchain. But don\'t worry, we\'re working on it! In the meantime you can check it in the explorer.
+ from: %s
+ to: %s
In progress…
You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d
This one that you are holding in your hands and the other one with number %s.\n\nBoth cards can be used to extract funds from this wallet.
@@ -427,7 +434,6 @@
Single-currency
My Wallets
Unlock all with %s
- Create PayString
Transaction history
Network is unreachable
Blockchain is unreachable. Try later
@@ -449,6 +455,7 @@
Can\'t send transaction. Not enough funds.
Failed to establish WalletConnect session. Please, try again later.
Failed to establish WalletConnect session: timeout error. Please, try again later.
+ Session request contains unsupported blockchains for WalletConnect connection. Unsupported blockchains:\n
Connection with this Dapp cannot be established due to its technical implementation.
%s network not found. Please, add it first and try again.
Paste from clipboard
diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts
index 1dc31c8335..94bb016bdc 100644
--- a/core/ui/build.gradle.kts
+++ b/core/ui/build.gradle.kts
@@ -9,8 +9,10 @@ dependencies {
implementation(deps.androidx.fragment.ktx)
/** Compose */
+ implementation(deps.compose.constraintLayout)
implementation(deps.compose.foundation)
implementation(deps.compose.material)
+ implementation(deps.compose.material3)
implementation(deps.compose.ui.tooling)
/** Other libraries */
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 00a6b2ac03..5ca9a16de8 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
@@ -312,14 +312,8 @@ private fun TangemButton(
textStyle: TextStyle = TangemTheme.typography.button,
) {
Button(
- modifier = modifier
- .width(IntrinsicSize.Min)
- .heightIn(min = size.toHeightDp()),
- onClick = {
- if (!showProgress) {
- onClick()
- }
- },
+ modifier = modifier.heightIn(min = size.toHeightDp()),
+ onClick = { if (!showProgress) onClick() },
enabled = enabled,
elevation = elevation,
shape = size.toShape(),
@@ -588,59 +582,44 @@ private open class TangemButtonColors(
// region Preview
@Composable
-private fun PrimaryButtonSample(modifier: Modifier = Modifier) {
+private fun PrimaryButtonSample() {
Column(
- modifier = modifier
- .background(TangemTheme.colors.background.primary),
+ modifier = Modifier.background(TangemTheme.colors.background.primary),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
- PrimaryButton(
- modifier = Modifier.fillMaxWidth(),
- text = "Manage tokens",
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- PrimaryButton(
- modifier = Modifier.fillMaxWidth(),
- showProgress = true,
- text = "Manage tokens",
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
+ PrimaryButton(modifier = Modifier.fillMaxWidth(), text = "Manage tokens", onClick = { })
+ PrimaryButton(modifier = Modifier.fillMaxWidth(), showProgress = true, text = "Manage tokens", onClick = { })
PrimaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
iconResId = R.drawable.ic_tangem_24,
- onClick = { /* no-op */ },
+ onClick = { },
)
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
PrimaryButtonIconStart(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
iconResId = R.drawable.ic_tangem_24,
- onClick = { /* no-op */ },
+ onClick = { },
)
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
enabled = false,
- onClick = { /* no-op */ },
+ onClick = { },
)
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
PrimaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
iconResId = R.drawable.ic_tangem_24,
enabled = false,
- onClick = { /* no-op */ },
+ onClick = { },
)
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
PrimaryButtonIconStart(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
iconResId = R.drawable.ic_tangem_24,
enabled = false,
- onClick = { /* no-op */ },
+ onClick = { },
)
}
}
@@ -662,59 +641,44 @@ private fun PrimaryButtonPreview_Dark() {
}
@Composable
-private fun SecondaryButtonSample(modifier: Modifier = Modifier) {
+private fun SecondaryButtonSample() {
Column(
- modifier = modifier
- .background(TangemTheme.colors.background.primary),
+ modifier = Modifier.background(TangemTheme.colors.background.primary),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
- SecondaryButton(
- modifier = Modifier.fillMaxWidth(),
- text = "Manage tokens",
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- SecondaryButton(
- modifier = Modifier.fillMaxWidth(),
- showProgress = true,
- text = "Manage tokens",
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
+ SecondaryButton(modifier = Modifier.fillMaxWidth(), text = "Manage tokens", onClick = { })
+ SecondaryButton(modifier = Modifier.fillMaxWidth(), showProgress = true, text = "Manage tokens", onClick = { })
SecondaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
iconResId = R.drawable.ic_tangem_24,
- onClick = { /* no-op */ },
+ onClick = { },
)
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
SecondaryButtonIconStart(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
iconResId = R.drawable.ic_tangem_24,
- onClick = { /* no-op */ },
+ onClick = { },
)
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
SecondaryButton(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
enabled = false,
- onClick = { /* no-op */ },
+ onClick = { },
)
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
SecondaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
iconResId = R.drawable.ic_tangem_24,
enabled = false,
- onClick = { /* no-op */ },
+ onClick = { },
)
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
SecondaryButtonIconStart(
modifier = Modifier.fillMaxWidth(),
text = "Manage tokens",
iconResId = R.drawable.ic_tangem_24,
enabled = false,
- onClick = { /* no-op */ },
+ onClick = { },
)
}
}
@@ -736,50 +700,23 @@ private fun SecondaryButtonPreview_Dark() {
}
@Composable
-private fun TextButtonSample(modifier: Modifier = Modifier) {
+private fun TextButtonSample() {
Column(
- modifier = modifier
- .background(TangemTheme.colors.background.primary),
+ modifier = Modifier.background(TangemTheme.colors.background.primary),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
- TextButton(
- text = "Enabled",
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- TextButtonIconStart(
- text = "Enabled",
- iconResId = R.drawable.ic_plus_24,
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- TextButton(
- text = "Enabled",
- enabled = false,
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- TextButtonIconStart(
- text = "Enabled",
- iconResId = R.drawable.ic_plus_24,
- enabled = false,
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- WarningTextButton(
- text = "Delete",
- onClick = { /* no-op */ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- SelectorButton(
- text = "USD",
- onClick = { /* no-op */ },
- )
+ TextButton(text = "Enabled", onClick = { })
+ TextButtonIconStart(text = "Enabled", iconResId = R.drawable.ic_plus_24, onClick = { })
+ TextButton(text = "Enabled", enabled = false, onClick = { })
+ TextButtonIconStart(text = "Enabled", iconResId = R.drawable.ic_plus_24, enabled = false, onClick = { })
+ WarningTextButton(text = "Delete", onClick = { })
+ SelectorButton(text = "USD", onClick = { })
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
-private fun TextButtonPreview_Light() {
+private fun TextButtonPreview_LightTheme() {
TangemTheme {
TextButtonSample()
}
@@ -787,62 +724,30 @@ private fun TextButtonPreview_Light() {
@Preview(showBackground = true, widthDp = 360)
@Composable
-private fun TextButtonPreview_Dark() {
+private fun TextButtonPreview_DarkTheme() {
TangemTheme(isDark = true) {
TextButtonSample()
}
}
@Composable
-private fun ActionButtonSample(modifier: Modifier = Modifier) {
+private fun ActionButtonSample() {
Column(
- modifier = modifier
- .background(TangemTheme.colors.background.primary),
+ modifier = Modifier.background(TangemTheme.colors.background.primary),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
- RoundedActionButton(
- text = "Send",
- iconResId = R.drawable.ic_arrow_up_24,
- onClick = { /* [REDACTED_TODO_COMMENT]*/ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- ActionButton(
- text = "Send",
- iconResId = R.drawable.ic_arrow_up_24,
- onClick = { /* [REDACTED_TODO_COMMENT]*/ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- BackgroundActionButton(
- text = "Send",
- iconResId = R.drawable.ic_arrow_up_24,
- onClick = { /* [REDACTED_TODO_COMMENT]*/ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- RoundedActionButton(
- text = "Send",
- iconResId = R.drawable.ic_arrow_up_24,
- enabled = false,
- onClick = { /* [REDACTED_TODO_COMMENT]*/ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- ActionButton(
- text = "Send",
- iconResId = R.drawable.ic_arrow_up_24,
- enabled = false,
- onClick = { /* [REDACTED_TODO_COMMENT]*/ },
- )
- Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8))
- BackgroundActionButton(
- text = "Send",
- iconResId = R.drawable.ic_arrow_up_24,
- enabled = false,
- onClick = { /* [REDACTED_TODO_COMMENT]*/ },
- )
+ RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
+ ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
+ BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, onClick = { })
+ RoundedActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
+ ActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
+ BackgroundActionButton(text = "Send", iconResId = R.drawable.ic_arrow_up_24, enabled = false, onClick = { })
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
-private fun ActionButtonPreview_Light() {
+private fun ActionButtonPreview_LightTheme() {
TangemTheme {
ActionButtonSample()
}
@@ -850,7 +755,7 @@ private fun ActionButtonPreview_Light() {
@Preview(showBackground = true, widthDp = 360)
@Composable
-private fun ActionButtonPreview_Dark() {
+private fun ActionButtonPreview_DarkTheme() {
TangemTheme(isDark = true) {
ActionButtonSample()
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Notifications.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Notifications.kt
deleted file mode 100644
index e70f571b18..0000000000
--- a/core/ui/src/main/java/com/tangem/core/ui/components/Notifications.kt
+++ /dev/null
@@ -1,202 +0,0 @@
-package com.tangem.core.ui.components
-
-import androidx.annotation.DrawableRes
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.tooling.preview.Preview
-import com.tangem.core.ui.R
-import com.tangem.core.ui.res.TangemTheme
-
-/**
- * Closable notification with custom icon
- * Child of parent component
- * @see Figma component
- *
- * Use to show banner with custom icon and possibility to close
- * i.e. Feedback notification
- *
- * @param title notification title
- * @param icon drawable res on icon
- * @param iconColor icon color
- * @param onClick callback on click
- * @param onCloseClick callback on close icon click
- */
-@Composable
-fun ClosableNotification(
- title: String,
- @DrawableRes icon: Int,
- iconColor: Color,
- onClick: (() -> Unit),
- onCloseClick: (() -> Unit),
-) {
- NotificationCardTemplate(onClick) {
- Icon(
- modifier = Modifier
- .size(TangemTheme.dimens.size20)
- .align(Alignment.CenterStart),
- painter = painterResource(id = icon),
- tint = iconColor,
- contentDescription = null,
- )
- Text(
- modifier = Modifier
- .padding(horizontal = TangemTheme.dimens.spacing28)
- .align(Alignment.CenterStart),
- text = title,
- color = TangemTheme.colors.text.primary1,
- style = TangemTheme.typography.subtitle2,
- )
- Icon(
- modifier = Modifier
- .size(TangemTheme.dimens.size20)
- .align(Alignment.CenterEnd)
- .clickable(onClick = onCloseClick),
- painter = painterResource(id = R.drawable.ic_close_24),
- contentDescription = null,
- tint = TangemTheme.colors.icon.informative,
- )
- }
-}
-
-/**
- * Notification component from Design system
- * There are few states for this component, but only one parent, see link below
- *
- * Use this for Notification with title, subtitle, clickable or not
- *
- * @param title notification title
- * @param subtitle notification subtitle
- * @param onClick click on notification, if its null then no chevron icon
- *
- * @see Figma component
- */
-@Composable
-fun WarningNotification(title: String, subtitle: String?, onClick: (() -> Unit)?) {
- NotificationCardTemplate(onClick) {
- Image(
- modifier = Modifier
- .size(TangemTheme.dimens.size20)
- .align(Alignment.CenterStart),
- painter = painterResource(id = R.drawable.img_attention_20),
- contentDescription = null,
- )
- Column(
- modifier = Modifier
- .padding(horizontal = TangemTheme.dimens.spacing28)
- .align(Alignment.CenterStart),
- ) {
- Text(
- text = title,
- color = TangemTheme.colors.text.primary1,
- style = TangemTheme.typography.subtitle2,
- )
- if (!subtitle.isNullOrEmpty()) {
- SpacerH2()
- Text(
- text = subtitle,
- color = TangemTheme.colors.text.tertiary,
- style = TangemTheme.typography.caption,
- )
- }
- }
- if (onClick != null) {
- Icon(
- modifier = Modifier
- .size(TangemTheme.dimens.size20)
- .align(Alignment.CenterEnd),
- painter = painterResource(id = R.drawable.ic_chevron_right_24),
- contentDescription = null,
- tint = TangemTheme.colors.icon.informative,
- )
- }
- }
-}
-
-@OptIn(ExperimentalMaterialApi::class)
-@Composable
-private fun NotificationCardTemplate(onClick: (() -> Unit)? = null, content: @Composable BoxScope.() -> Unit) {
- Surface(
- color = TangemTheme.colors.button.secondary,
- shape = RoundedCornerShape(TangemTheme.dimens.radius18),
- onClick = onClick ?: {},
- enabled = onClick != null,
- ) {
- Box(
- Modifier
- .padding(
- horizontal = TangemTheme.dimens.spacing12,
- vertical = TangemTheme.dimens.spacing8,
- )
- .wrapContentSize(),
- ) {
- content()
- }
- }
-}
-
-// region Preview
-
-@Composable
-private fun WarningNotificationPreview() {
- Column(modifier = Modifier.fillMaxWidth()) {
- WarningNotification(
- 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...",
- onClick = {},
- )
- SpacerH32()
- WarningNotification(
- title = "Your wallet hasn’t been backed up",
- subtitle = null,
- onClick = {},
- )
- SpacerH32()
- WarningNotification(
- 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...",
- onClick = null,
- )
- SpacerH32()
- WarningNotification(
- title = "Your wallet hasn’t been backed up",
- subtitle = null,
- onClick = null,
- )
- SpacerH32()
- ClosableNotification(
- title = "Like tangem app?",
- icon = R.drawable.ic_star_24,
- iconColor = TangemTheme.colors.icon.attention,
- onClick = {},
- onCloseClick = {},
- )
- }
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun Preview_WarningNotification_InLightTheme() {
- TangemTheme(isDark = false) {
- WarningNotificationPreview()
- }
-}
-
-@Preview(showBackground = true)
-@Composable
-private fun Preview_WarningNotification_InDarkTheme() {
- TangemTheme(isDark = true) {
- WarningNotificationPreview()
- }
-}
-
-// endregion Preview
\ No newline at end of file
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
new file mode 100644
index 0000000000..db338c9721
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt
@@ -0,0 +1,171 @@
+package com.tangem.core.ui.components.notifications
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+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.res.TangemColorPalette
+import com.tangem.core.ui.res.TangemTheme
+
+/**
+ * Notification component from Design system.
+ * Use this for Notification with title, subtitle, clickable or not.
+ *
+ * @param state component state
+ * @param modifier modifier
+ *
+ * @see Figma component
+ */
+@Composable
+fun Notification(state: NotificationState, modifier: Modifier = Modifier) {
+ Surface(
+ onClick = if (state is NotificationState.Action) {
+ state.onClick
+ } else {
+ {}
+ },
+ modifier = modifier,
+ enabled = when (state) {
+ is NotificationState.Simple -> false
+ is NotificationState.Action -> true
+ },
+ shape = RoundedCornerShape(TangemTheme.dimens.radius18),
+ color = TangemTheme.colors.button.secondary,
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing8),
+ ) {
+ NotificationIcon(
+ iconResId = state.iconResId,
+ iconTint = state.tint,
+ modifier = Modifier
+ .size(size = TangemTheme.dimens.size20)
+ .align(alignment = Alignment.CenterStart),
+ )
+
+ NotificationInfoBlock(
+ title = state.title,
+ subtitle = state.subtitle,
+ modifier = Modifier.align(alignment = Alignment.CenterStart),
+ )
+
+ if (state is NotificationState.Action) {
+ Icon(
+ modifier = Modifier
+ .size(size = TangemTheme.dimens.size20)
+ .align(alignment = Alignment.CenterEnd),
+ painter = painterResource(id = R.drawable.ic_chevron_right_24),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.informative,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun NotificationIcon(@DrawableRes iconResId: Int, iconTint: Color?, modifier: Modifier = Modifier) {
+ if (iconTint != null) {
+ Icon(
+ painter = painterResource(id = iconResId),
+ contentDescription = null,
+ modifier = modifier,
+ tint = iconTint,
+ )
+ } else {
+ Image(
+ painter = painterResource(id = iconResId),
+ contentDescription = null,
+ modifier = modifier,
+ )
+ }
+}
+
+@Composable
+private fun NotificationInfoBlock(title: String, subtitle: String?, modifier: Modifier = Modifier) {
+ Column(modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing30)) {
+ Text(
+ text = title,
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.body2,
+ )
+
+ if (!subtitle.isNullOrEmpty()) {
+ SpacerH2()
+ Text(
+ text = subtitle,
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.caption,
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun Preview_WarningNotification_Light(
+ @PreviewParameter(NotificationStateProvider::class)
+ state: NotificationState,
+) {
+ TangemTheme(isDark = false) {
+ Notification(state)
+ }
+}
+
+@Preview
+@Composable
+private fun Preview_WarningNotification_Dark(
+ @PreviewParameter(NotificationStateProvider::class)
+ state: NotificationState,
+) {
+ TangemTheme(isDark = true) {
+ Notification(state)
+ }
+}
+
+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...",
+ iconResId = R.drawable.img_attention_20,
+ ),
+ NotificationState.Simple(
+ title = "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...",
+ iconResId = R.drawable.img_attention_20,
+ onClick = {},
+ ),
+ NotificationState.Action(
+ title = "Your wallet hasn’t been backed up",
+ subtitle = null,
+ iconResId = R.drawable.ic_alert_circle_24,
+ tint = TangemColorPalette.Amaranth,
+ onClick = {},
+ ),
+ ),
+)
\ 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
new file mode 100644
index 0000000000..065fa1e125
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationState.kt
@@ -0,0 +1,54 @@
+package com.tangem.core.ui.components.notifications
+
+import androidx.annotation.DrawableRes
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Notification component state
+ *
+ * @property title title
+ * @property subtitle subtitle
+ * @property iconResId icon resource id
+ * @property tint icon tint
+ *
+[REDACTED_AUTHOR]
+ */
+sealed class NotificationState(
+ open val title: String,
+ open val subtitle: String? = null,
+ @DrawableRes open val iconResId: Int,
+ open val tint: Color? = null,
+) {
+
+ /**
+ * Simple notification state. Non clickable.
+ *
+ * @property title title
+ * @property subtitle subtitle
+ * @property iconResId icon resource id
+ * @property tint icon tint
+ */
+ data class Simple(
+ override val title: String,
+ override val subtitle: String? = null,
+ @DrawableRes override val iconResId: Int,
+ override val tint: Color? = null,
+ ) : NotificationState(title, subtitle, iconResId, tint)
+
+ /**
+ * Clickable notification state
+ *
+ * @property title title
+ * @property subtitle subtitle
+ * @property iconResId icon resource id
+ * @property tint icon tint
+ * @param onClick lambda be invoked when notification component is clicked
+ */
+ data class Action(
+ override val title: String,
+ override val subtitle: String? = null,
+ @DrawableRes override val iconResId: Int,
+ override val tint: Color? = null,
+ val onClick: () -> Unit,
+ ) : NotificationState(title, subtitle, iconResId, tint)
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
new file mode 100644
index 0000000000..1257a022a7
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
@@ -0,0 +1,355 @@
+package com.tangem.core.ui.components.transactions
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import androidx.constraintlayout.compose.ConstraintLayout
+import androidx.constraintlayout.compose.Dimension
+import com.tangem.core.ui.R
+import com.tangem.core.ui.components.CircleShimmer
+import com.tangem.core.ui.components.RectangleShimmer
+import com.tangem.core.ui.res.TangemTheme
+
+/**
+ * Transaction component
+ *
+ * @param state state
+ * @param modifier modifier
+ *
+ * @see Figma Component
+ *
+[REDACTED_AUTHOR]
+ */
+@Composable
+fun Transaction(state: TransactionState, modifier: Modifier = Modifier) {
+ Surface(
+ modifier = modifier
+ .background(TangemTheme.colors.background.primary)
+ .defaultMinSize(minHeight = TangemTheme.dimens.size56)
+ .padding(horizontal = TangemTheme.dimens.spacing12, vertical = TangemTheme.dimens.spacing10),
+ color = TangemTheme.colors.background.primary,
+ ) {
+ @Suppress("DestructuringDeclarationWithTooManyEntries")
+ ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
+ val (iconItem, titleItem, subtitleItem, amountItem, timestampItem) = createRefs()
+
+ Icon(
+ state = state,
+ modifier = Modifier
+ .size(TangemTheme.dimens.size40)
+ .constrainAs(iconItem) {
+ start.linkTo(parent.start)
+ centerVerticallyTo(parent)
+ },
+ )
+
+ Title(
+ state = state,
+ modifier = Modifier
+ .padding(horizontal = TangemTheme.dimens.spacing12)
+ .constrainAs(titleItem) {
+ start.linkTo(iconItem.end)
+ top.linkTo(iconItem.top)
+ },
+ )
+
+ Subtitle(
+ state = state,
+ modifier = Modifier
+ .padding(top = TangemTheme.dimens.spacing6)
+ .padding(horizontal = TangemTheme.dimens.spacing12)
+ .constrainAs(subtitleItem) {
+ start.linkTo(iconItem.end)
+ top.linkTo(amountItem.bottom)
+ end.linkTo(timestampItem.start)
+ width = Dimension.fillToConstraints
+ },
+ )
+
+ Amount(
+ state = state,
+ modifier = Modifier.constrainAs(amountItem) {
+ start.linkTo(titleItem.end)
+ top.linkTo(titleItem.top)
+ end.linkTo(parent.end)
+ width = Dimension.fillToConstraints
+ },
+ )
+
+ Timestamp(
+ state = state,
+ modifier = Modifier
+ .padding(top = TangemTheme.dimens.spacing6)
+ .constrainAs(timestampItem) {
+ top.linkTo(amountItem.bottom)
+ end.linkTo(parent.end)
+ },
+ )
+ }
+ }
+}
+
+@Composable
+private fun Icon(state: TransactionState, modifier: Modifier = Modifier) {
+ when (state) {
+ is TransactionState.Content -> {
+ Box(
+ modifier = modifier
+ .size(TangemTheme.dimens.size40)
+ .background(
+ color = when (state) {
+ is TransactionState.ProcessedTransactionContent -> {
+ TangemTheme.colors.icon.attention.copy(alpha = 0.1f)
+ }
+ is TransactionState.CompletedTransactionContent -> {
+ TangemTheme.colors.background.secondary
+ }
+ },
+ shape = CircleShape,
+ ),
+ ) {
+ Icon(
+ painter = painterResource(
+ id = when (state) {
+ is TransactionState.Sending,
+ is TransactionState.Send,
+ -> R.drawable.ic_arrow_up_24
+
+ is TransactionState.Receiving,
+ is TransactionState.Receive,
+ -> R.drawable.ic_arrow_down_24
+
+ is TransactionState.Approving,
+ is TransactionState.Approved,
+ -> R.drawable.ic_doc_24
+
+ is TransactionState.Swapping,
+ is TransactionState.Swapped,
+ -> R.drawable.ic_exchange_vertical_24
+ },
+ ),
+ contentDescription = null,
+ modifier = Modifier
+ .size(TangemTheme.dimens.size20)
+ .align(Alignment.Center),
+ tint = when (state) {
+ is TransactionState.ProcessedTransactionContent -> TangemTheme.colors.icon.attention
+ is TransactionState.CompletedTransactionContent -> TangemTheme.colors.icon.informative
+ },
+ )
+ }
+ }
+ is TransactionState.Loading -> {
+ CircleShimmer(modifier = modifier.size(TangemTheme.dimens.size40))
+ }
+ }
+}
+
+@Composable
+private fun Title(state: TransactionState, modifier: Modifier = Modifier) {
+ when (state) {
+ is TransactionState.ProcessedTransactionContent -> {
+ Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6)) {
+ Text(
+ text = stringResource(
+ id = when (state) {
+ is TransactionState.Sending -> R.string.common_transfer
+ is TransactionState.Receiving -> R.string.common_transfer
+ is TransactionState.Approving -> R.string.common_approval
+ is TransactionState.Swapping -> R.string.common_swap
+ },
+ ),
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.subtitle2,
+ )
+
+ Image(
+ modifier = Modifier.align(Alignment.CenterVertically),
+ painter = painterResource(id = R.drawable.img_loader_15),
+ contentDescription = null,
+ )
+ }
+ }
+ is TransactionState.CompletedTransactionContent -> {
+ Text(
+ text = stringResource(
+ id = when (state) {
+ is TransactionState.Send -> R.string.common_transfer
+ is TransactionState.Receive -> R.string.common_transfer
+ is TransactionState.Approved -> R.string.common_approval
+ is TransactionState.Swapped -> R.string.common_swap
+ },
+ ),
+ modifier = modifier,
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.subtitle2,
+ )
+ }
+ is TransactionState.Loading -> {
+ RectangleShimmer(
+ modifier = modifier.size(width = TangemTheme.dimens.size70, height = TangemTheme.dimens.size12),
+ )
+ }
+ }
+}
+
+@Composable
+private fun Subtitle(state: TransactionState, modifier: Modifier = Modifier) {
+ when (state) {
+ is TransactionState.Content -> {
+ Text(
+ text = when (state) {
+ is TransactionState.Sending,
+ is TransactionState.Send,
+ -> stringResource(
+ id = R.string.transaction_history_transaction_to_address,
+ state.address,
+ )
+ is TransactionState.Receiving,
+ is TransactionState.Receive,
+ is TransactionState.Approving,
+ is TransactionState.Approved,
+ -> stringResource(
+ id = R.string.transaction_history_transaction_from_address,
+ state.address,
+ )
+ is TransactionState.Swapping,
+ is TransactionState.Swapped,
+ -> stringResource(
+ id = R.string.transaction_history_contract_address,
+ state.address,
+ )
+ },
+ modifier = modifier,
+ textAlign = TextAlign.Start,
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.caption,
+ )
+ }
+ is TransactionState.Loading -> {
+ RectangleShimmer(
+ modifier = modifier.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12),
+ )
+ }
+ }
+}
+
+@Composable
+private fun Amount(state: TransactionState, modifier: Modifier = Modifier) {
+ when (state) {
+ is TransactionState.Content -> {
+ Text(
+ text = state.amount,
+ modifier = modifier,
+ textAlign = TextAlign.End,
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.body2,
+ )
+ }
+ is TransactionState.Loading -> {
+ RectangleShimmer(
+ modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
+ )
+ }
+ }
+}
+
+@Composable
+private fun Timestamp(state: TransactionState, modifier: Modifier = Modifier) {
+ when (state) {
+ is TransactionState.Content -> {
+ Text(
+ text = state.timestamp,
+ modifier = modifier,
+ textAlign = TextAlign.End,
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.caption,
+ )
+ }
+ is TransactionState.Loading -> {
+ RectangleShimmer(
+ modifier = modifier.size(width = TangemTheme.dimens.size40, height = TangemTheme.dimens.size12),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun Preview_TransactionItem_LightTheme(
+ @PreviewParameter(TransactionItemStateProvider::class) state: TransactionState,
+) {
+ TangemTheme(isDark = false) {
+ Transaction(state)
+ }
+}
+
+@Preview
+@Composable
+private fun Preview_TransactionItem_DarkTheme(
+ @PreviewParameter(TransactionItemStateProvider::class) state: TransactionState,
+) {
+ TangemTheme(isDark = true) {
+ Transaction(state)
+ }
+}
+
+private class TransactionItemStateProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ TransactionState.Sending(
+ address = "33BddS...ga2B",
+ amount = "-0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ TransactionState.Receiving(
+ address = "33BddS...ga2B",
+ amount = "+0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ TransactionState.Approving(
+ address = "33BddS...ga2B",
+ amount = "+0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ TransactionState.Swapping(
+ address = "33BddS...ga2B",
+ amount = "+0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ TransactionState.Send(
+ address = "33BddS...ga2B",
+ amount = "-0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ TransactionState.Receive(
+ address = "33BddS...ga2B",
+ amount = "+0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ TransactionState.Approved(
+ address = "33BddS...ga2B",
+ amount = "+0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ TransactionState.Swapped(
+ address = "33BddS...ga2B",
+ amount = "+0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ TransactionState.Loading,
+ ),
+)
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt
new file mode 100644
index 0000000000..f0502a4a21
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt
@@ -0,0 +1,155 @@
+package com.tangem.core.ui.components.transactions
+
+/**
+ * Transaction component state
+ *
+[REDACTED_AUTHOR]
+ */
+sealed interface TransactionState {
+
+ /**
+ * Content state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ sealed class Content(
+ open val address: String,
+ open val amount: String,
+ open val timestamp: String,
+ ) : TransactionState
+
+ /**
+ * Content state for processed transaction
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ sealed class ProcessedTransactionContent(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : Content(address, amount, timestamp)
+
+ /**
+ * Content state for completed transaction
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ sealed class CompletedTransactionContent(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : Content(address, amount, timestamp)
+
+ /**
+ * Processed sending transaction state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ data class Sending(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : ProcessedTransactionContent(address, amount, timestamp)
+
+ /**
+ * Processed receiving transaction state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ data class Receiving(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : ProcessedTransactionContent(address, amount, timestamp)
+
+ /**
+ * Processed approving transaction state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ data class Approving(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : ProcessedTransactionContent(address, amount, timestamp)
+
+ /**
+ * Processed swapping transaction state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ data class Swapping(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : ProcessedTransactionContent(address, amount, timestamp)
+
+ /**
+ * Completed sending transaction state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ data class Send(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : CompletedTransactionContent(address, amount, timestamp)
+
+ /**
+ * Completed receiving transaction state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ data class Receive(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : CompletedTransactionContent(address, amount, timestamp)
+
+ /**
+ * Completed approving transaction state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ data class Approved(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : CompletedTransactionContent(address, amount, timestamp)
+
+ /**
+ * Completed swapping transaction state
+ *
+ * @property address address
+ * @property amount amount
+ * @property timestamp timestamp
+ */
+ data class Swapped(
+ override val address: String,
+ override val amount: String,
+ override val timestamp: String,
+ ) : CompletedTransactionContent(address, amount, timestamp)
+
+ /** Loading state */
+ object Loading : TransactionState
+}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt
index e70b0d7015..a27dcb58c4 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt
@@ -2,22 +2,42 @@ package com.tangem.core.ui.extensions
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
+/**
+ * Utility class for creating text as [String] or [StringRes].
+ * It necessary to use [Immutable] annotation because all sealed interface has runtime stability.
+ * All subclasses are stable.
+ */
+@Immutable
sealed interface TextReference {
- class Res(@StringRes val id: Int, val formatArgs: List = emptyList()) : TextReference {
- constructor(@StringRes id: Int, vararg formatArgs: Any) : this(id, formatArgs.toList())
- }
- class Str(val value: String) : TextReference
+ /**
+ * Text resource id
+ *
+ * @property id resource id
+ * @property formatArgs arguments
+ *
+ * Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is unstable.
+ */
+ data class Res(@StringRes val id: Int, val formatArgs: WrappedList = WrappedList(emptyList())) : TextReference
+
+ /**
+ * Text string
+ *
+ * @property value value
+ */
+ data class Str(val value: String) : TextReference
}
+/** Get text */
@Composable
@ReadOnlyComposable
fun TextReference.resolveReference(): String {
return when (this) {
- is TextReference.Res -> stringResource(this.id, *this.formatArgs.toTypedArray())
- is TextReference.Str -> this.value
+ is TextReference.Res -> stringResource(id, *formatArgs.toTypedArray())
+ is TextReference.Str -> value
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/WrappedList.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/WrappedList.kt
new file mode 100644
index 0000000000..6f529c9574
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/WrappedList.kt
@@ -0,0 +1,10 @@
+package com.tangem.core.ui.extensions
+
+import androidx.compose.runtime.Immutable
+
+/**
+[REDACTED_AUTHOR]
+ */
+@JvmInline
+@Immutable
+value class WrappedList(val data: List) : List by data
\ 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 c2fbac9891..8c85ac6339 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
@@ -56,9 +56,11 @@ data class TangemDimens internal constructor(
val size46: Dp = 46.dp,
val size48: Dp = 48.dp,
val size50: Dp = 50.dp,
+ val size52: Dp = 52.dp,
val size56: Dp = 56.dp,
val size62: Dp = 62.dp,
val size68: Dp = 68.dp,
+ val size70: Dp = 70.dp,
val size72: Dp = 72.dp,
val size80: Dp = 80.dp,
val size84: Dp = 84.dp,
@@ -74,6 +76,7 @@ data class TangemDimens internal constructor(
val size200: Dp = 200.dp,
// endregion Size
// region Spacing
+ val spacing0: Dp = 0.dp,
val spacing0_5: Dp = 0.5.dp,
val spacing2: Dp = 2.dp,
val spacing4: Dp = 4.dp,
@@ -89,6 +92,7 @@ data class TangemDimens internal constructor(
val spacing24: Dp = 24.dp,
val spacing26: Dp = 26.dp,
val spacing28: Dp = 28.dp,
+ val spacing30: Dp = 30.dp,
val spacing32: Dp = 32.dp,
val spacing34: Dp = 34.dp,
val spacing36: Dp = 34.dp,
diff --git a/core/ui/src/main/res/drawable/ic_compass_24.xml b/core/ui/src/main/res/drawable/ic_compass_24.xml
new file mode 100644
index 0000000000..597c8d959f
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_compass_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_doc_24.xml b/core/ui/src/main/res/drawable/ic_doc_24.xml
new file mode 100644
index 0000000000..8855153e59
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_doc_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_filter_24.xml b/core/ui/src/main/res/drawable/ic_filter_24.xml
new file mode 100644
index 0000000000..b88ca7dcef
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_filter_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_group_24.xml b/core/ui/src/main/res/drawable/ic_group_24.xml
new file mode 100644
index 0000000000..0f5f5f4487
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_group_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_sort_24.xml b/core/ui/src/main/res/drawable/ic_sort_24.xml
new file mode 100644
index 0000000000..538a24d8e0
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_sort_24.xml
@@ -0,0 +1,9 @@
+
+
+
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 151aecafc4..de6329b343 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
@@ -83,7 +82,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"
@@ -144,7 +142,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"
@@ -172,6 +169,4 @@ 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)
\ No newline at end of file
diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt
index 938566bd50..b04b901b66 100644
--- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt
+++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ReferralFragment.kt
@@ -4,11 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.compose.foundation.layout.systemBarsPadding
+import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
-import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.transition.TransitionInflater
+import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.referral.presentation.R
import com.tangem.feature.referral.router.ReferralRouter
import com.tangem.feature.referral.ui.ReferralScreen
@@ -28,13 +30,17 @@ class ReferralFragment : Fragment() {
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
viewModel.setRouter(ReferralRouter(fragmentManager = WeakReference(parentFragmentManager)))
viewModel.onScreenOpened()
return ComposeView(inflater.context).apply {
isTransitionGroup = true
setContent {
- ReferralScreen(stateHolder = viewModel.uiState)
+ TangemTheme {
+ ReferralScreen(
+ modifier = Modifier.systemBarsPadding(),
+ stateHolder = viewModel.uiState,
+ )
+ }
}
}
}
diff --git a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
index 7f1ff6b6a8..7c3e8b2ddf 100644
--- a/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
+++ b/features/referral/presentation/src/main/java/com/tangem/feature/referral/ui/ReferralScreen.kt
@@ -5,41 +5,11 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.LocalOverscrollConfiguration
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.BottomSheetScaffold
-import androidx.compose.material.BottomSheetState
-import androidx.compose.material.BottomSheetValue
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.Icon
-import androidx.compose.material.Snackbar
-import androidx.compose.material.SnackbarDuration
-import androidx.compose.material.SnackbarHost
-import androidx.compose.material.SnackbarHostState
-import androidx.compose.material.SnackbarResult
-import androidx.compose.material.Text
-import androidx.compose.material.rememberBottomSheetScaffoldState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
+import androidx.compose.material.*
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -63,9 +33,7 @@ import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.referral.models.DemoModeException
import com.tangem.feature.referral.models.ReferralStateHolder
-import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar
-import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoContentState
-import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState
+import com.tangem.feature.referral.models.ReferralStateHolder.*
import com.tangem.feature.referral.presentation.R
import com.valentinilk.shimmer.shimmer
import kotlinx.coroutines.launch
@@ -77,47 +45,46 @@ import kotlinx.coroutines.launch
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
-internal fun ReferralScreen(stateHolder: ReferralStateHolder) {
+internal fun ReferralScreen(stateHolder: ReferralStateHolder, modifier: Modifier = Modifier) {
val coroutineScope = rememberCoroutineScope()
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed),
)
- TangemTheme {
- BottomSheetScaffold(
- sheetContent = {
- AgreementBottomSheetContent(
- url = when (val state = stateHolder.referralInfoState) {
- is ReferralInfoState.NonParticipantContent -> state.url
- is ReferralInfoState.ParticipantContent -> state.url
- is ReferralInfoState.Loading -> ""
- },
- )
- },
- scaffoldState = bottomSheetScaffoldState,
- sheetShape = RoundedCornerShape(
- topStart = TangemTheme.dimens.radius16,
- topEnd = TangemTheme.dimens.radius16,
- ),
- sheetElevation = TangemTheme.dimens.elevation24,
- sheetPeekHeight = TangemTheme.dimens.size0,
- content = {
- ReferralContent(
- stateHolder = stateHolder,
- onAgreementClick = {
- stateHolder.analytics.onAgreementClicked.invoke()
- coroutineScope.launch {
- if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) {
- bottomSheetScaffoldState.bottomSheetState.expand()
- } else {
- bottomSheetScaffoldState.bottomSheetState.collapse()
- }
+ BottomSheetScaffold(
+ modifier = modifier,
+ sheetContent = {
+ AgreementBottomSheetContent(
+ url = when (val state = stateHolder.referralInfoState) {
+ is ReferralInfoState.NonParticipantContent -> state.url
+ is ReferralInfoState.ParticipantContent -> state.url
+ is ReferralInfoState.Loading -> ""
+ },
+ )
+ },
+ scaffoldState = bottomSheetScaffoldState,
+ sheetShape = RoundedCornerShape(
+ topStart = TangemTheme.dimens.radius16,
+ topEnd = TangemTheme.dimens.radius16,
+ ),
+ sheetElevation = TangemTheme.dimens.elevation24,
+ sheetPeekHeight = TangemTheme.dimens.size0,
+ content = {
+ ReferralContent(
+ stateHolder = stateHolder,
+ onAgreementClick = {
+ stateHolder.analytics.onAgreementClicked.invoke()
+ coroutineScope.launch {
+ if (bottomSheetScaffoldState.bottomSheetState.isCollapsed) {
+ bottomSheetScaffoldState.bottomSheetState.expand()
+ } else {
+ bottomSheetScaffoldState.bottomSheetState.collapse()
}
- },
- )
- },
- )
- }
+ }
+ },
+ )
+ },
+ )
}
@OptIn(ExperimentalFoundationApi::class)
@@ -487,7 +454,7 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() {
TangemTheme(isDark = false) {
ReferralScreen(
stateHolder = ReferralStateHolder(
- headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
+ headerState = HeaderState(onBackClicked = {}),
referralInfoState = ReferralInfoState.ParticipantContent(
award = "10 USDT",
networkName = "Tron",
@@ -499,7 +466,7 @@ private fun Preview_ReferralScreen_Participant_InLightTheme() {
url = "",
),
errorSnackbar = null,
- analytics = ReferralStateHolder.Analytics(
+ analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
onShareClicked = {},
@@ -515,7 +482,7 @@ private fun Preview_ReferralScreen_Participant_InDarkTheme() {
TangemTheme(isDark = true) {
ReferralScreen(
stateHolder = ReferralStateHolder(
- headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
+ headerState = HeaderState(onBackClicked = {}),
referralInfoState = ReferralInfoState.ParticipantContent(
award = "10 USDT",
networkName = "Tron",
@@ -527,7 +494,7 @@ private fun Preview_ReferralScreen_Participant_InDarkTheme() {
url = "",
),
errorSnackbar = null,
- analytics = ReferralStateHolder.Analytics(
+ analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
onShareClicked = {},
@@ -543,7 +510,7 @@ private fun Preview_ReferralScreen_NonParticipant_InLightTheme() {
TangemTheme(isDark = false) {
ReferralScreen(
stateHolder = ReferralStateHolder(
- headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
+ headerState = HeaderState(onBackClicked = {}),
referralInfoState = ReferralInfoState.NonParticipantContent(
award = "10 USDT",
networkName = "Tron",
@@ -552,7 +519,7 @@ private fun Preview_ReferralScreen_NonParticipant_InLightTheme() {
onParticipateClicked = {},
),
errorSnackbar = null,
- analytics = ReferralStateHolder.Analytics(
+ analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
onShareClicked = {},
@@ -568,7 +535,7 @@ private fun Preview_ReferralScreen_NonParticipant_InDarkTheme() {
TangemTheme(isDark = true) {
ReferralScreen(
stateHolder = ReferralStateHolder(
- headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
+ headerState = HeaderState(onBackClicked = {}),
referralInfoState = ReferralInfoState.NonParticipantContent(
award = "10 USDT",
networkName = "Tron",
@@ -577,7 +544,7 @@ private fun Preview_ReferralScreen_NonParticipant_InDarkTheme() {
onParticipateClicked = {},
),
errorSnackbar = null,
- analytics = ReferralStateHolder.Analytics(
+ analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
onShareClicked = {},
@@ -593,10 +560,10 @@ private fun Preview_ReferralScreen_Loading_InLightTheme() {
TangemTheme(isDark = false) {
ReferralScreen(
stateHolder = ReferralStateHolder(
- headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
+ headerState = HeaderState(onBackClicked = {}),
referralInfoState = ReferralInfoState.Loading,
errorSnackbar = null,
- analytics = ReferralStateHolder.Analytics(
+ analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
onShareClicked = {},
@@ -612,10 +579,10 @@ private fun Preview_ReferralScreen_Loading_InDarkTheme() {
TangemTheme(isDark = true) {
ReferralScreen(
stateHolder = ReferralStateHolder(
- headerState = ReferralStateHolder.HeaderState(onBackClicked = {}),
+ headerState = HeaderState(onBackClicked = {}),
referralInfoState = ReferralInfoState.Loading,
errorSnackbar = null,
- analytics = ReferralStateHolder.Analytics(
+ analytics = Analytics(
onAgreementClicked = {},
onCopyClicked = {},
onShareClicked = {},
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
index f5a39df29e..acad315b28 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt
@@ -54,7 +54,7 @@ internal fun SwapScreenContent(state: SwapStateHolder, onPermissionWarningClick:
Column {
AppBarWithBackButton(
- text = stringResource(R.string.swapping_swap),
+ text = stringResource(R.string.common_swap),
onBackClick = state.onBackClicked,
iconRes = R.drawable.ic_close_24,
)
@@ -348,7 +348,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
else -> {
PrimaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
- text = stringResource(id = R.string.swapping_swap),
+ text = stringResource(id = R.string.common_swap),
iconResId = R.drawable.ic_tangem_24,
enabled = state.swapButton.enabled,
showProgress = state.swapButton.loading,
@@ -384,7 +384,7 @@ private val receiveCard = SwapCardData(
coinId = "",
)
-val stateSelectable = SelectableItemsState(
+val stateSelectable = SelectableItemsState(
selectedItem = Item(
0,
TextReference.Str("Balance"),
diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt
index d8ad3e646a..2d6f3e52b4 100644
--- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt
+++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt
@@ -39,7 +39,7 @@ fun SwapSuccessScreen(state: SwapSuccessStateHolder, onBack: () -> Unit) {
},
topBar = {
AppBarWithBackButton(
- text = stringResource(R.string.swapping_swap),
+ text = stringResource(R.string.common_swap),
onBackClick = onBack,
iconRes = R.drawable.ic_close_24,
)
diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts
index 183e43c174..b8bbf291ab 100644
--- a/features/wallet/impl/build.gradle.kts
+++ b/features/wallet/impl/build.gradle.kts
@@ -22,6 +22,9 @@ dependencies {
implementation(deps.compose.ui.tooling)
implementation(deps.compose.shimmer)
implementation(deps.compose.accompanist.systemUiController)
+ implementation(deps.compose.reorderable)
+
+ /** Other libraries */
implementation(deps.kotlin.immutable.collections)
/** DI */
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt
index 1bc2cd04fd..954467d0fe 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/WalletFragment.kt
@@ -6,8 +6,9 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
-import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
+import com.tangem.core.ui.components.SystemBarsEffect
+import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.features.wallet.navigation.WalletRouter
@@ -32,8 +33,6 @@ internal class WalletFragment : Fragment() {
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
-
with(TransitionInflater.from(requireContext())) {
enterTransition = inflateTransition(R.transition.slide_right)
exitTransition = inflateTransition(R.transition.fade)
@@ -41,8 +40,15 @@ internal class WalletFragment : Fragment() {
return ComposeView(inflater.context).apply {
setContent {
- isTransitionGroup = true
- _walletRouter.Initialize()
+ TangemTheme {
+ val systemBarsColor = TangemTheme.colors.background.secondary
+ SystemBarsEffect {
+ setSystemBarsColor(systemBarsColor)
+ }
+
+ isTransitionGroup = true
+ _walletRouter.Initialize()
+ }
}
}
}
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 40817e750e..5fcea3bc6c 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,19 +1,22 @@
package com.tangem.feature.wallet.presentation.common
import com.tangem.core.ui.R
-import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState
+import com.tangem.core.ui.components.transactions.TransactionState
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.common.state.TokenListState
+import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
+import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder
-import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState
-import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
+import com.tangem.feature.wallet.presentation.wallet.state.*
import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toPersistentList
import java.util.UUID
internal object WalletPreviewData {
- val walletCardContent = WalletCardState.Content(
+ val walletTopBarConfig = WalletTopBarConfig(onScanCardClick = {}, onMoreClick = {})
+
+ val walletCardContentState = WalletCardState.Content(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
balance = "8923,05 $",
@@ -22,7 +25,7 @@ internal object WalletPreviewData {
onClick = {},
)
- val walletCardLoading = WalletCardState.Loading(
+ val walletCardLoadingState = WalletCardState.Loading(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
@@ -30,7 +33,7 @@ internal object WalletPreviewData {
onClick = {},
)
- val walletCardHiddenContent = WalletCardState.HiddenContent(
+ val walletCardHiddenContentState = WalletCardState.HiddenContent(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
@@ -38,7 +41,7 @@ internal object WalletPreviewData {
onClick = {},
)
- val walletCardError = WalletCardState.Error(
+ val walletCardErrorState = WalletCardState.Error(
id = UUID.randomUUID().toString(),
title = "Wallet 1",
additionalInfo = "3 cards • Seed enabled",
@@ -46,15 +49,6 @@ internal object WalletPreviewData {
onClick = {},
)
- val walletScreenState = WalletStateHolder(
- onBackClick = {},
- headerConfig = WalletStateHolder.HeaderConfig(
- wallets = persistentListOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError),
- onScanCardClick = {},
- onMoreClick = {},
- ),
- )
-
val tokenItemVisibleState = TokenItemState.Content(
id = UUID.randomUUID().toString(),
tokenIconUrl = null,
@@ -107,71 +101,177 @@ internal object WalletPreviewData {
val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString())
- val organizeTokensState = OrganizeTokensStateHolder(
- tokens = TokenListState.GroupedByNetwork(
- groups = persistentListOf(),
+ private const val networksSize = 10
+ private const val tokensSize = 3
+ val draggableItems = List(networksSize) { it }
+ .flatMap { index ->
+ val lastNetworkIndex = networksSize - 1
+ val networkNumber = index + 1
+
+ val group = DraggableItem.GroupHeader(
+ id = "group_$networkNumber",
+ networkName = "$networkNumber",
+ )
+
+ val tokens: MutableList = mutableListOf()
+ repeat(times = tokensSize) { i ->
+ val tokenNumber = i + 1
+ tokens.add(
+ DraggableItem.Token(
+ tokenItemState = tokenItemDragState.copy(
+ id = "${group.id}_token_$tokenNumber",
+ name = "Token $tokenNumber",
+ networkIconResId = R.drawable.img_eth_22.takeIf { i != 0 },
+ ),
+ groupId = group.id,
+ ),
+ )
+ }
+
+ val divider = DraggableItem.GroupPlaceholder(id = "divider_$networkNumber")
+
+ buildList {
+ add(group)
+ addAll(tokens)
+ if (index != lastNetworkIndex) {
+ add(divider)
+ }
+ }
+ }
+ .toPersistentList()
+
+ val draggableTokens = draggableItems
+ .filterIsInstance()
+ .toPersistentList()
+
+ val groupedOrganizeTokensState = OrganizeTokensStateHolder(
+ itemsState = OrganizeTokensListState.GroupedByNetwork(
+ items = draggableItems,
),
header = OrganizeTokensStateHolder.HeaderConfig(
onSortByBalanceClick = {},
onGroupByNetworkClick = {},
),
+ dragConfig = OrganizeTokensStateHolder.DragConfig(
+ onItemDragged = { _, _ -> },
+ onDragStart = {},
+ canDragItemOver = { _, _ -> false },
+ onItemDragEnd = {},
+ ),
actions = OrganizeTokensStateHolder.ActionsConfig(
onApplyClick = {},
onCancelClick = {},
),
)
- private val draggableTokenList = persistentListOf(
- tokenItemDragState.copy(
- id = "token_1",
- name = "Ethereum",
- tokenIconResId = R.drawable.img_eth_22,
- networkIconResId = null,
- fiatAmount = "3 172,14 $",
- ),
- tokenItemDragState.copy(
- id = "token_2",
- networkIconResId = R.drawable.img_eth_22,
- fiatAmount = "803,65 $",
- ),
- tokenItemDragState.copy(
- id = "token_3",
- name = "USDT",
- tokenIconResId = R.drawable.img_arbitrum_22,
- networkIconResId = R.drawable.img_eth_22,
- fiatAmount = "88,01 $",
+ val organizeTokensState = groupedOrganizeTokensState.copy(
+ itemsState = OrganizeTokensListState.Ungrouped(
+ items = draggableTokens,
),
)
- val networkGroup = NetworkGroupState.Content(
- id = UUID.randomUUID().toString(),
- networkName = "Ethereum",
- tokens = persistentListOf(
- tokenItemVisibleState.copy(
- id = "token_1",
- name = "Ethereum",
- tokenIconResId = R.drawable.img_eth_22,
- networkIconResId = null,
- amount = "1,89340821 ETH",
+ val multicurrencyWalletScreenState = WalletStateHolder.MultiCurrencyContent(
+ onBackClick = {},
+ topBarConfig = walletTopBarConfig,
+ selectedWallet = walletCardContentState,
+ wallets = persistentListOf(
+ walletCardContentState,
+ walletCardLoadingState,
+ walletCardHiddenContentState,
+ walletCardErrorState,
+ ),
+ contentItems = persistentListOf(
+ WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle("Bitcoin"),
+ WalletContentItemState.MultiCurrencyItem.Token(
+ tokenItemVisibleState.copy(
+ id = "token_1",
+ name = "Ethereum",
+ tokenIconResId = R.drawable.img_eth_22,
+ networkIconResId = null,
+ amount = "1,89340821 ETH",
+ ),
),
- tokenItemVisibleState.copy(
- id = "token_2",
- networkIconResId = R.drawable.img_eth_22,
- amount = "733,71097 MATIC",
+ WalletContentItemState.MultiCurrencyItem.Token(
+ tokenItemVisibleState.copy(
+ id = "token_2",
+ name = "Ethereum",
+ tokenIconResId = R.drawable.img_eth_22,
+ networkIconResId = null,
+ amount = "1,89340821 ETH",
+ ),
),
- tokenItemVisibleState.copy(
- id = "token_3",
- name = "USDT",
- tokenIconResId = R.drawable.img_arbitrum_22,
- networkIconResId = R.drawable.img_eth_22,
- amount = "0,25404523 ARB",
+ WalletContentItemState.MultiCurrencyItem.Token(
+ tokenItemVisibleState.copy(
+ id = "token_3",
+ name = "Ethereum",
+ tokenIconResId = R.drawable.img_eth_22,
+ networkIconResId = null,
+ amount = "1,89340821 ETH",
+ ),
+ ),
+ WalletContentItemState.MultiCurrencyItem.Token(
+ tokenItemVisibleState.copy(
+ id = "token_4",
+ name = "Ethereum",
+ tokenIconResId = R.drawable.img_eth_22,
+ networkIconResId = null,
+ amount = "1,89340821 ETH",
+ ),
+ ),
+ WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle("Ethereum"),
+ WalletContentItemState.MultiCurrencyItem.Token(
+ tokenItemVisibleState.copy(
+ id = "token_5",
+ name = "Ethereum",
+ tokenIconResId = R.drawable.img_eth_22,
+ networkIconResId = null,
+ amount = "1,89340821 ETH",
+ ),
),
),
+ notifications = persistentListOf(
+ WalletNotification.UnreachableNetworks,
+ WalletNotification.LikeTangemApp(onClick = {}),
+ WalletNotification.NeedToBackup(onClick = {}),
+ WalletNotification.ScanCard(onClick = {}),
+ ),
+ onOrganizeTokensClick = {},
)
- val draggableNetworkGroup = NetworkGroupState.Draggable(
- id = UUID.randomUUID().toString(),
- networkName = "Ethereum",
- tokens = draggableTokenList,
+ val singleWalletScreenState = WalletStateHolder.SingleCurrencyContent(
+ onBackClick = {},
+ topBarConfig = walletTopBarConfig,
+ selectedWallet = walletCardContentState,
+ wallets = persistentListOf(
+ walletCardContentState,
+ walletCardLoadingState,
+ walletCardHiddenContentState,
+ walletCardErrorState,
+ ),
+ contentItems = persistentListOf(
+ WalletContentItemState.SingleCurrencyItem.Title(onExploreClick = {}),
+ WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle("Today"),
+ WalletContentItemState.SingleCurrencyItem.Transaction(
+ TransactionState.Sending(
+ address = "33BddS...ga2B",
+ amount = "-0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ ),
+ WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle("Yesterday"),
+ WalletContentItemState.SingleCurrencyItem.Transaction(
+ TransactionState.Sending(
+ address = "33BddS...ga2B",
+ amount = "-0.500913 BTC",
+ timestamp = "8:41",
+ ),
+ ),
+ ),
+ notifications = persistentListOf(
+ WalletNotification.UnreachableNetworks,
+ WalletNotification.LikeTangemApp(onClick = {}),
+ WalletNotification.NeedToBackup(onClick = {}),
+ WalletNotification.ScanCard(onClick = {}),
+ ),
)
}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt
index ee2d924e85..ae4d06b0dc 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@@ -15,65 +14,102 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
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.NetworkGroupState
+import org.burnoutcrew.reorderable.ReorderableLazyListState
+import org.burnoutcrew.reorderable.detectReorder
@Composable
-internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Modifier) {
- Column(modifier = modifier) {
- Row(
- modifier = Modifier
- .background(TangemTheme.colors.background.primary)
- .padding(horizontal = TangemTheme.dimens.spacing12)
- .fillMaxWidth()
- .heightIn(min = TangemTheme.dimens.size48),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween,
- ) {
- Text(
- text = stringResource(id = R.string.wallet_network_group_title, state.networkName),
- style = TangemTheme.typography.subtitle2,
- color = TangemTheme.colors.text.tertiary,
- )
- if (state is NetworkGroupState.Draggable) {
+internal fun NetworkGroupItem(networkName: String, modifier: Modifier = Modifier) {
+ InternalNetworkGroupItem(
+ modifier = modifier,
+ networkName = networkName,
+ )
+}
+
+@Composable
+internal fun DraggableNetworkGroupItem(
+ networkName: String,
+ modifier: Modifier = Modifier,
+ reorderableTokenListState: ReorderableLazyListState? = null,
+) {
+ InternalNetworkGroupItem(
+ modifier = modifier,
+ networkName = networkName,
+ endIcon = {
+ Box(
+ modifier = Modifier
+ .size(TangemTheme.dimens.size32)
+ .let {
+ if (reorderableTokenListState != null) {
+ it.detectReorder(reorderableTokenListState)
+ } else {
+ it
+ }
+ },
+ contentAlignment = Alignment.Center,
+ ) {
Icon(
painter = painterResource(id = R.drawable.ic_group_drop_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
- }
- Column {
- state.tokens.forEach { token ->
- key(token.id) {
- TokenItem(state = token)
- }
- }
+ },
+ )
+}
+
+@Composable
+private fun InternalNetworkGroupItem(
+ networkName: String,
+ modifier: Modifier = Modifier,
+ endIcon: @Composable RowScope.() -> Unit = {},
+) {
+ Column(modifier = modifier) {
+ Row(
+ modifier = Modifier
+ .background(TangemTheme.colors.background.primary)
+ .padding(horizontal = TangemTheme.dimens.spacing14)
+ .fillMaxWidth()
+ .heightIn(min = TangemTheme.dimens.size48),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = stringResource(id = R.string.wallet_network_group_title, networkName),
+ style = TangemTheme.typography.subtitle2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ endIcon()
}
}
}
// region Preview
+@Composable
+private fun NetworkGroupItemSample(isDraggable: Boolean) {
+ if (isDraggable) {
+ DraggableNetworkGroupItem(networkName = "Ethereum")
+ } else {
+ NetworkGroupItem(networkName = "Ethereum")
+ }
+}
+
@Preview(showBackground = true, widthDp = 360)
@Composable
-private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) {
+private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) isDraggable: Boolean) {
TangemTheme {
- NetworkGroupItem(group)
+ NetworkGroupItemSample(isDraggable)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
-private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) {
+private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) isDraggable: Boolean) {
TangemTheme(isDark = true) {
- NetworkGroupItem(group)
+ NetworkGroupItemSample(isDraggable)
}
}
-private class NetworkGroupProvider : CollectionPreviewParameterProvider(
- collection = listOf(
- WalletPreviewData.networkGroup,
- WalletPreviewData.draggableNetworkGroup,
- ),
+private class NetworkGroupProvider : CollectionPreviewParameterProvider(
+ collection = listOf(true, false),
)
// endregion Preview
\ 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 78c5b5f322..e615c7dedb 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
@@ -9,6 +9,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -18,6 +19,7 @@ import androidx.compose.ui.res.painterResource
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.dp
import androidx.constraintlayout.compose.ConstrainedLayoutReference
import androidx.constraintlayout.compose.ConstraintLayout
@@ -32,15 +34,21 @@ import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState
+import org.burnoutcrew.reorderable.ReorderableLazyListState
+import org.burnoutcrew.reorderable.detectReorder
private const val DOTS = "•••"
+val TOKEN_ITEM_HEIGHT: Dp
+ @Composable
+ @ReadOnlyComposable
+ get() = TangemTheme.dimens.size68
@Composable
internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) {
when (state) {
is TokenItemState.Content -> ContentTokenItem(state, modifier)
is TokenItemState.Loading -> LoadingTokenItem(modifier)
- is TokenItemState.Draggable -> DraggableTokenItem(state, modifier)
+ is TokenItemState.Draggable -> DraggableTokenItem(state, modifier, reorderableTokenListState = null)
is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier)
}
}
@@ -65,7 +73,11 @@ private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier
}
@Composable
-internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modifier = Modifier) {
+internal fun DraggableTokenItem(
+ state: TokenItemState.Draggable,
+ modifier: Modifier = Modifier,
+ reorderableTokenListState: ReorderableLazyListState? = null,
+) {
InternalTokenItem(
modifier = modifier,
name = state.name,
@@ -75,12 +87,25 @@ internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modif
amount = state.fiatAmount,
hasPending = false,
options = { ref ->
- Icon(
- modifier = Modifier.constrainAsOptionsItem(scope = this, ref),
- painter = painterResource(id = R.drawable.ic_drag_24),
- tint = TangemTheme.colors.icon.informative,
- contentDescription = null,
- )
+ Box(
+ modifier = Modifier
+ .size(TangemTheme.dimens.size32)
+ .constrainAsOptionsItem(scope = this, ref)
+ .let {
+ if (reorderableTokenListState != null) {
+ it.detectReorder(reorderableTokenListState)
+ } else {
+ it
+ }
+ },
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_drag_24),
+ tint = TangemTheme.colors.icon.informative,
+ contentDescription = null,
+ )
+ }
},
)
}
@@ -199,7 +224,7 @@ private fun InternalTokenItem(
modifier = Modifier
.fillMaxWidth()
.padding(
- horizontal = TangemTheme.dimens.spacing12,
+ horizontal = TangemTheme.dimens.spacing14,
vertical = TangemTheme.dimens.spacing4,
),
) {
@@ -251,7 +276,7 @@ private fun BaseSurface(
content: @Composable () -> Unit,
) {
Surface(
- modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68),
+ modifier = modifier.defaultMinSize(minHeight = TOKEN_ITEM_HEIGHT),
color = TangemTheme.colors.background.primary,
onClick = onClick ?: {},
enabled = onClick != null,
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt
deleted file mode 100644
index c34fdd126d..0000000000
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/NetworkGroupState.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.tangem.feature.wallet.presentation.common.state
-
-import androidx.compose.runtime.Immutable
-import kotlinx.collections.immutable.ImmutableList
-
-@Immutable
-internal sealed interface NetworkGroupState {
- val id: String
- val networkName: String
- val tokens: ImmutableList
-
- data class Draggable(
- override val id: String,
- override val networkName: String,
- override val tokens: ImmutableList,
- ) : NetworkGroupState
-
- data class Content(
- override val id: String,
- override val networkName: String,
- override val tokens: ImmutableList,
- ) : NetworkGroupState
-}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt
deleted file mode 100644
index fe3b7fba46..0000000000
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.tangem.feature.wallet.presentation.common.state
-
-import androidx.compose.runtime.Immutable
-import kotlinx.collections.immutable.ImmutableList
-
-@Immutable
-internal sealed interface TokenListState {
-
- data class GroupedByNetwork(
- val groups: ImmutableList,
- ) : TokenListState
-
- data class Ungrouped(
- val tokens: ImmutableList,
- ) : TokenListState
-}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt
index e626eb0029..4f3cda259d 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensScreen.kt
@@ -1,88 +1,313 @@
-// TODO: Remove after components implementation
-@file:Suppress("UNUSED_PARAMETER")
-
package com.tangem.feature.wallet.presentation.organizetokens
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
+import androidx.compose.foundation.lazy.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.AppBarDefaults
+import androidx.compose.material3.FabPosition
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+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.BackgroundActionButton
+import com.tangem.core.ui.components.PrimaryButton
+import com.tangem.core.ui.components.SecondaryButton
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.TokenListState
+import com.tangem.feature.wallet.presentation.common.component.DraggableNetworkGroupItem
+import com.tangem.feature.wallet.presentation.common.component.DraggableTokenItem
+import org.burnoutcrew.reorderable.ReorderableItem
+import org.burnoutcrew.reorderable.ReorderableLazyListState
+import org.burnoutcrew.reorderable.rememberReorderableLazyListState
+import org.burnoutcrew.reorderable.reorderable
@Composable
-internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder) {
+internal fun OrganizeTokensScreen(state: OrganizeTokensStateHolder, modifier: Modifier = Modifier) {
+ val tokensListState = rememberLazyListState()
+
Scaffold(
+ modifier = modifier,
topBar = {
- TopBar(state.header)
+ TopBar(state.header, tokensListState)
},
content = { paddingValues ->
TokenList(
- state = state.tokens,
modifier = Modifier.padding(paddingValues),
+ listState = tokensListState,
+ state = state.itemsState,
+ dragConfig = state.dragConfig,
)
},
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
Actions(state.actions)
},
- contentColor = TangemTheme.colors.background.primary,
+ containerColor = TangemTheme.colors.background.secondary,
)
}
+// TODO: Fix list animations
@Composable
-private fun TokenList(state: TokenListState, modifier: Modifier = Modifier) {
- when (state) {
- is TokenListState.GroupedByNetwork -> {
- /* [REDACTED_TODO_COMMENT] */
+private fun TokenList(
+ listState: LazyListState,
+ state: OrganizeTokensListState,
+ dragConfig: OrganizeTokensStateHolder.DragConfig,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier = modifier) {
+ val reorderableListState = rememberReorderableLazyListState(
+ onMove = dragConfig.onItemDragged,
+ listState = listState,
+ canDragOver = dragConfig.canDragItemOver,
+ onDragEnd = { _, _ -> dragConfig.onItemDragEnd() },
+ )
+ val items = state.items
+ val lastItemIndex = items.lastIndex
+
+ LazyColumn(
+ modifier = Modifier
+ .reorderable(reorderableListState)
+ .align(Alignment.TopCenter)
+ .padding(horizontal = TangemTheme.dimens.spacing16),
+ state = reorderableListState.listState,
+ contentPadding = PaddingValues(
+ top = TangemTheme.dimens.spacing12,
+ bottom = TangemTheme.dimens.spacing92,
+ ),
+ ) {
+ itemsIndexed(
+ items = items,
+ key = { _, item -> item.id },
+ ) { index, item ->
+
+ val onDragStart = remember(item) {
+ { dragConfig.onDragStart(item) }
+ }
+
+ DraggableItem(
+ item = item,
+ index = index,
+ lastItemIndex = lastItemIndex,
+ reorderableState = reorderableListState,
+ onDragStart = onDragStart,
+ )
+
+ if (item is DraggableItem.GroupPlaceholder) {
+ // This item should be displayed in the list but remain invisible
+ Box(modifier = Modifier.fillMaxWidth())
+ }
+ }
}
- is TokenListState.Ungrouped -> {
- /* [REDACTED_TODO_COMMENT] */
+
+ BottomGradient(modifier = Modifier.align(Alignment.BottomCenter))
+ }
+}
+
+@Composable
+private fun LazyItemScope.DraggableItem(
+ index: Int,
+ item: DraggableItem,
+ lastItemIndex: Int,
+ reorderableState: ReorderableLazyListState,
+ onDragStart: () -> Unit,
+) {
+ ReorderableItem(
+ reorderableState = reorderableState,
+ index = index,
+ key = item.id,
+ ) { isDragging ->
+
+ if (isDragging) {
+ onDragStart()
+ }
+
+ val itemModifier = Modifier
+ .clipFirstLastAndDraggingItems(index, lastItemIndex, isDragging)
+
+ when (item) {
+ is DraggableItem.GroupHeader -> DraggableNetworkGroupItem(
+ modifier = itemModifier,
+ networkName = item.networkName,
+ reorderableTokenListState = reorderableState,
+ )
+ is DraggableItem.Token -> DraggableTokenItem(
+ modifier = itemModifier,
+ state = item.tokenItemState,
+ reorderableTokenListState = reorderableState,
+ )
+ is DraggableItem.GroupPlaceholder -> Unit
}
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
-private fun TopBar(config: OrganizeTokensStateHolder.HeaderConfig, modifier: Modifier = Modifier) {
- TopAppBar(
- title = {
- Text(
- text = "Organize tokens", // TODO: Move to resources
- style = TangemTheme.typography.subtitle1,
- color = TangemTheme.colors.text.primary1,
- maxLines = 1,
- )
- },
- colors = TopAppBarDefaults.topAppBarColors(
- containerColor = TangemTheme.colors.background.secondary,
- titleContentColor = TangemTheme.colors.text.primary1,
- ),
+private fun BottomGradient(modifier: Modifier = Modifier) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(TangemTheme.dimens.size116)
+ .background(
+ brush = Brush.verticalGradient(
+ colors = listOf(
+ Color.Transparent,
+ TangemTheme.colors.background.secondary,
+ ),
+ ),
+ ),
)
}
@Composable
-private fun Actions(config: OrganizeTokensStateHolder.ActionsConfig, modifier: Modifier = Modifier) {
- // TODO: Implement list apply and cancel actions
+private fun TopBar(
+ config: OrganizeTokensStateHolder.HeaderConfig,
+ tokensListState: LazyListState,
+ modifier: Modifier = Modifier,
+) {
+ val isElevationEnabled by remember {
+ derivedStateOf {
+ tokensListState.firstVisibleItemScrollOffset > 0
+ }
+ }
+ val elevation by animateDpAsState(
+ targetValue = if (isElevationEnabled) AppBarDefaults.TopAppBarElevation else TangemTheme.dimens.elevation0,
+ label = "top_bar_shadow_elevation",
+ )
+
+ Column(
+ modifier = modifier
+ .shadow(elevation)
+ .background(TangemTheme.colors.background.secondary)
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .fillMaxWidth(),
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(min = TangemTheme.dimens.size56),
+ contentAlignment = Alignment.CenterStart,
+ ) {
+ Text(
+ text = stringResource(id = R.string.organize_tokens_title),
+ style = TangemTheme.typography.subtitle1,
+ color = TangemTheme.colors.text.primary1,
+ maxLines = 1,
+ )
+ }
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(min = TangemTheme.dimens.size56),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
+ ) {
+ BackgroundActionButton(
+ modifier = Modifier.weight(1f),
+ text = stringResource(id = R.string.organize_tokens_sort_by_balance),
+ iconResId = R.drawable.ic_sort_24,
+ onClick = config.onSortByBalanceClick,
+ )
+ BackgroundActionButton(
+ modifier = Modifier.weight(1f),
+ text = stringResource(id = R.string.organize_tokens_group),
+ iconResId = R.drawable.ic_group_24,
+ onClick = config.onGroupByNetworkClick,
+ )
+ }
+ }
}
+@Composable
+private fun Actions(config: OrganizeTokensStateHolder.ActionsConfig, modifier: Modifier = Modifier) {
+ Row(
+ modifier = modifier
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
+ ) {
+ SecondaryButton(
+ modifier = Modifier.weight(1f),
+ text = stringResource(id = R.string.common_cancel),
+ onClick = config.onCancelClick,
+ )
+ PrimaryButton(
+ modifier = Modifier.weight(1f),
+ text = stringResource(id = R.string.common_apply),
+ onClick = config.onApplyClick,
+ )
+ }
+}
+
+private fun Modifier.clipFirstLastAndDraggingItems(index: Int, lastItemIndex: Int, isDragging: Boolean): Modifier =
+ composed {
+ when {
+ isDragging -> {
+ val elevation by animateDpAsState(
+ targetValue = TangemTheme.dimens.elevation12,
+ label = "dragging_item_shadow_elevation",
+ )
+
+ this.shadow(elevation, shape = TangemTheme.shapes.roundedCornersXMedium)
+ }
+ index == 0 -> {
+ this.clip(
+ RoundedCornerShape(
+ topStart = TangemTheme.dimens.radius16,
+ topEnd = TangemTheme.dimens.radius16,
+ ),
+ )
+ }
+ index == lastItemIndex -> {
+ this.clip(
+ RoundedCornerShape(
+ bottomStart = TangemTheme.dimens.radius16,
+ bottomEnd = TangemTheme.dimens.radius16,
+ ),
+ )
+ }
+ else -> this
+ }
+ }
+
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Composable
-private fun OrganizeTokensScreenPreview_Light() {
+private fun OrganizeTokensScreenPreview_Light(
+ @PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensStateHolder,
+) {
TangemTheme {
- OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState)
+ OrganizeTokensScreen(state)
}
}
@Preview(showBackground = true, widthDp = 360)
@Composable
-private fun OrganizeTokensScreenPreview_Dark() {
+private fun OrganizeTokensScreenPreview_Dark(
+ @PreviewParameter(OrganizeTokensStateProvider::class) state: OrganizeTokensStateHolder,
+) {
TangemTheme(isDark = true) {
- OrganizeTokensScreen(state = WalletPreviewData.organizeTokensState)
+ OrganizeTokensScreen(state)
}
}
+
+private class OrganizeTokensStateProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ WalletPreviewData.organizeTokensState,
+ WalletPreviewData.groupedOrganizeTokensState,
+ ),
+)
// endregion Preview
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt
index b9a8e797cb..693b9ce419 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensStateHolder.kt
@@ -1,10 +1,15 @@
package com.tangem.feature.wallet.presentation.organizetokens
-import com.tangem.feature.wallet.presentation.common.state.TokenListState
+import androidx.compose.runtime.Immutable
+import com.tangem.feature.wallet.presentation.common.state.TokenItemState
+import kotlinx.collections.immutable.PersistentList
+import kotlinx.collections.immutable.toPersistentList
+import org.burnoutcrew.reorderable.ItemPosition
internal data class OrganizeTokensStateHolder(
- val tokens: TokenListState,
val header: HeaderConfig,
+ val itemsState: OrganizeTokensListState,
+ val dragConfig: DragConfig,
val actions: ActionsConfig,
) {
@@ -17,4 +22,79 @@ internal data class OrganizeTokensStateHolder(
val onApplyClick: () -> Unit,
val onCancelClick: () -> Unit,
)
+
+ data class DragConfig(
+ val onItemDragged: (from: ItemPosition, to: ItemPosition) -> Unit,
+ val canDragItemOver: (dragOver: ItemPosition, dragging: ItemPosition) -> Boolean,
+ val onItemDragEnd: () -> Unit,
+ val onDragStart: (item: DraggableItem) -> Unit,
+ )
+}
+
+@Immutable
+internal sealed interface OrganizeTokensListState {
+ val items: PersistentList
+
+ data class GroupedByNetwork(
+ override val items: PersistentList,
+ ) : OrganizeTokensListState
+
+ data class Ungrouped(
+ override val items: PersistentList,
+ ) : OrganizeTokensListState
+
+ @Suppress("UNCHECKED_CAST")
+ fun updateItems(update: (PersistentList) -> List): OrganizeTokensListState {
+ val updatedItems = update(this.items).toPersistentList()
+
+ return when (this) {
+ is GroupedByNetwork -> this.copy(items = updatedItems)
+ is Ungrouped -> this.copy(items = updatedItems as PersistentList)
+ }
+ }
+}
+
+/**
+ * Helper class for the DND list items
+ *
+ * @property id ID of the item
+ * */
+@Immutable
+internal sealed interface DraggableItem {
+ val id: String
+
+ /**
+ * Item for network group header.
+ *
+ * @property id ID of the network group
+ * @property networkName network group name
+ * */
+ data class GroupHeader(
+ override val id: String,
+ val networkName: String,
+ ) : DraggableItem
+
+ /**
+ * Item for token.
+ *
+ * @property tokenItemState state of the token item
+ * @property groupId ID of the network group which contains this token
+ * @property id ID of the token
+ * */
+ data class Token(
+ val tokenItemState: TokenItemState.Draggable,
+ val groupId: String,
+ ) : DraggableItem {
+ override val id: String = tokenItemState.id
+ }
+
+ /**
+ * Helper item used to detect possible positions where a network group can be placed.
+ * Used only on [OrganizeTokensListState.GroupedByNetwork] and placed between network groups.
+ *
+ * @property id ID of the placeholder
+ * */
+ data class GroupPlaceholder(
+ override val id: String,
+ ) : DraggableItem
}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt
index 16f726eab1..9334ce7f8b 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt
@@ -5,18 +5,123 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
+import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder.DragConfig
+import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder.HeaderConfig
+import com.tangem.feature.wallet.presentation.organizetokens.utils.checkCanMoveHeaderOver
+import com.tangem.feature.wallet.presentation.organizetokens.utils.checkCanMoveTokenOver
+import com.tangem.feature.wallet.presentation.organizetokens.utils.findItemsToMove
+import com.tangem.feature.wallet.presentation.organizetokens.utils.moveItem
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.collections.immutable.toPersistentList
+import org.burnoutcrew.reorderable.ItemPosition
import javax.inject.Inject
import kotlin.properties.Delegates
+// FIXME: Implemented with preview data
@HiltViewModel
internal class OrganizeTokensViewModel @Inject constructor() : ViewModel() {
+ // TODO: Move to domain
+ @Volatile
+ private var groupIdToTokens: Map>? = null
+
var router: InnerWalletRouter by Delegates.notNull()
var uiState: OrganizeTokensStateHolder by mutableStateOf(getInitialState())
private set
- private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState
+ private fun getInitialState(): OrganizeTokensStateHolder = WalletPreviewData.organizeTokensState.copy(
+ itemsState = OrganizeTokensListState.Ungrouped(
+ items = WalletPreviewData.draggableTokens,
+ ),
+ dragConfig = DragConfig(
+ onItemDragged = this::moveItem,
+ canDragItemOver = this::checkCanMoveItemOver,
+ onItemDragEnd = this::expandGroups,
+ onDragStart = this::collapseGroup,
+ ),
+ header = HeaderConfig(
+ onSortByBalanceClick = { /* no-op */ },
+ onGroupByNetworkClick = this::toggleTokensByNetworkGrouping,
+ ),
+ )
+
+ private fun toggleTokensByNetworkGrouping() {
+ val newListState = when (val itemsState = uiState.itemsState) {
+ is OrganizeTokensListState.GroupedByNetwork -> OrganizeTokensListState.Ungrouped(
+ items = itemsState.items.filterIsInstance().toPersistentList(),
+ )
+ is OrganizeTokensListState.Ungrouped -> OrganizeTokensListState.GroupedByNetwork(
+ items = WalletPreviewData.draggableItems,
+ )
+ }
+
+ uiState = uiState.copy(itemsState = newListState)
+ }
+
+ private fun checkCanMoveItemOver(moveOverItemPosition: ItemPosition, movedItemPosition: ItemPosition): Boolean {
+ val items = (uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork)
+ ?.items
+ ?: return true // If ungrouped then item can be moved anywhere
+
+ val (moveOverItem, movedItem) = items.findItemsToMove(moveOverItemPosition.key, movedItemPosition.key)
+
+ if (moveOverItem == null || movedItem == null) {
+ return false
+ }
+
+ return when (movedItem) {
+ is DraggableItem.GroupHeader -> checkCanMoveHeaderOver(moveOverItemPosition, moveOverItem, items.lastIndex)
+ is DraggableItem.Token -> checkCanMoveTokenOver(movedItem, moveOverItem)
+ is DraggableItem.GroupPlaceholder -> false
+ }
+ }
+
+ private fun collapseGroup(item: DraggableItem) {
+ if (!groupIdToTokens.isNullOrEmpty() || item is DraggableItem.Token) return
+
+ val itemsState = uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork ?: return
+ groupIdToTokens = itemsState.items
+ .asSequence()
+ .filterIsInstance()
+ .groupBy(DraggableItem.Token::groupId)
+
+ uiState = uiState.copy(
+ itemsState = itemsState.updateItems { items ->
+ items.filterNot { it is DraggableItem.Token && it.groupId == item.id }
+ },
+ )
+ }
+
+ private fun expandGroups() {
+ if (groupIdToTokens.isNullOrEmpty()) return
+
+ val itemsState = uiState.itemsState as? OrganizeTokensListState.GroupedByNetwork ?: return
+ val currentGroups = itemsState.items.filterIsInstance()
+ val newItems = currentGroups
+ .flatMapIndexed { index, group ->
+ mutableListOf(group)
+ .also { it.addAll(groupIdToTokens?.get(group.id).orEmpty()) }
+ .also {
+ if (index != currentGroups.lastIndex) {
+ it.add(DraggableItem.GroupPlaceholder(id = "group_divider_$index"))
+ }
+ }
+ }
+
+ uiState = uiState.copy(
+ itemsState = itemsState.updateItems { newItems },
+ )
+
+ groupIdToTokens = null
+ }
+
+ private fun moveItem(from: ItemPosition, to: ItemPosition) {
+ uiState = uiState.copy(
+ itemsState = uiState.itemsState.updateItems {
+ it.moveItem(from.index, to.index)
+ },
+ )
+ }
}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/DraggableItemsOperations.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/DraggableItemsOperations.kt
new file mode 100644
index 0000000000..d8f3733dc3
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/utils/DraggableItemsOperations.kt
@@ -0,0 +1,55 @@
+package com.tangem.feature.wallet.presentation.organizetokens.utils
+
+import com.tangem.feature.wallet.presentation.organizetokens.DraggableItem
+import kotlinx.collections.immutable.PersistentList
+import org.burnoutcrew.reorderable.ItemPosition
+
+internal fun List.findItemsToMove(
+ moveOverItemKey: Any?,
+ movedItemKey: Any?,
+): Pair {
+ var moveOverItem: DraggableItem? = null
+ var movedItem: DraggableItem? = null
+
+ for (item in this) {
+ if (item.id == moveOverItemKey) {
+ moveOverItem = item
+ }
+ if (item.id == movedItemKey) {
+ movedItem = item
+ }
+ if (moveOverItem != null && movedItem != null) {
+ break
+ }
+ }
+
+ return Pair(moveOverItem, movedItem)
+}
+
+internal fun checkCanMoveHeaderOver(
+ moveOverItemPosition: ItemPosition,
+ moveOverItem: DraggableItem,
+ lastItemIndex: Int,
+): Boolean {
+ return when {
+ moveOverItemPosition.index == 0 -> true
+ moveOverItemPosition.index == lastItemIndex -> true
+ moveOverItem is DraggableItem.GroupPlaceholder -> true
+ else -> false
+ }
+}
+
+internal fun checkCanMoveTokenOver(item: DraggableItem.Token, moveOverItem: DraggableItem): Boolean {
+ return when (moveOverItem) {
+ is DraggableItem.GroupHeader -> false // Token item can not be moved to group item
+ is DraggableItem.Token -> item.groupId == moveOverItem.groupId // Token item can not be moved over its group
+ is DraggableItem.GroupPlaceholder -> false
+ }
+}
+
+internal fun PersistentList.moveItem(fromIndex: Int, toIndex: Int): PersistentList {
+ val fromItem = this[fromIndex]
+ return this
+ .removeAt(fromIndex)
+ .add(toIndex, fromItem)
+}
\ 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 f2768ed116..b963f8f7b7 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
@@ -1,7 +1,9 @@
package com.tangem.feature.wallet.presentation.router
import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import androidx.fragment.app.Fragment
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavHostController
@@ -32,7 +34,10 @@ internal class DefaultWalletRouter : InnerWalletRouter {
) {
composable(WalletScreens.WALLET.name) {
val viewModel = hiltViewModel().apply { router = this@DefaultWalletRouter }
- WalletScreen(state = viewModel.uiState)
+ WalletScreen(
+ modifier = Modifier.systemBarsPadding(),
+ state = viewModel.uiState,
+ )
}
composable(WalletScreens.ORGANIZE_TOKENS.name) {
@@ -41,7 +46,10 @@ internal class DefaultWalletRouter : InnerWalletRouter {
val viewModel: OrganizeTokensViewModel = hiltViewModel()
.apply { router = this@DefaultWalletRouter }
- OrganizeTokensScreen(state = viewModel.uiState)
+ OrganizeTokensScreen(
+ modifier = Modifier.systemBarsPadding(),
+ 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 7685a267da..2f000581e2 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
@@ -1,9 +1,12 @@
package com.tangem.feature.wallet.presentation.wallet.state
import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Immutable
/** Wallet card state */
+@Immutable
internal sealed interface WalletCardState {
+
/** Id */
val id: String
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt
new file mode 100644
index 0000000000..f8934f6002
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletContentItemState.kt
@@ -0,0 +1,55 @@
+package com.tangem.feature.wallet.presentation.wallet.state
+
+import com.tangem.core.ui.components.transactions.TransactionState
+import com.tangem.feature.wallet.presentation.common.state.TokenItemState
+
+/**
+ * Wallet screen content item state
+ *
+[REDACTED_AUTHOR]
+ */
+internal sealed interface WalletContentItemState {
+
+ /** Multi currency wallet content state */
+ sealed interface MultiCurrencyItem : WalletContentItemState {
+
+ /**
+ * Network group title item
+ *
+ * @property networkName network name
+ */
+ data class NetworkGroupTitle(val networkName: String) : MultiCurrencyItem
+
+ /**
+ * Token item
+ *
+ * @property state token item state
+ */
+ data class Token(val state: TokenItemState) : MultiCurrencyItem
+ }
+
+ /** Single currency wallet content state */
+ sealed interface SingleCurrencyItem : WalletContentItemState {
+
+ /**
+ * Title item
+ *
+ * @property onExploreClick lambda be invoke when explore button was clicked
+ */
+ data class Title(val onExploreClick: () -> Unit) : SingleCurrencyItem
+
+ /**
+ * Transaction group title item
+ *
+ * @property title title
+ */
+ data class TransactionGroupTitle(val title: String) : SingleCurrencyItem
+
+ /**
+ * Transaction item
+ *
+ * @property state transaction state
+ */
+ data class Transaction(val state: TransactionState) : SingleCurrencyItem
+ }
+}
\ 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
new file mode 100644
index 0000000000..39e760dd4f
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt
@@ -0,0 +1,65 @@
+package com.tangem.feature.wallet.presentation.wallet.state
+
+import com.tangem.core.ui.components.notifications.NotificationState
+import com.tangem.core.ui.res.TangemColorPalette
+import com.tangem.feature.wallet.impl.R
+
+/**
+ * Wallet notification component state
+ *
+ * @property state state
+ *
+[REDACTED_AUTHOR]
+ */
+sealed class WalletNotification(open val state: NotificationState) {
+
+ /**
+ * "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,
+ onClick = onClick,
+ tint = TangemColorPalette.Amaranth,
+ ),
+ )
+
+ /** "Unreachable networks" notification */
+ object UnreachableNetworks : WalletNotification(
+ state = NotificationState.Simple(
+ title = "Some networks are unreachable",
+ iconResId = R.drawable.img_attention_20,
+ tint = null,
+ ),
+ )
+
+ /**
+ * "Like Tangem App" notification
+ *
+ * @property onClick lambda be invoked when notification is clicked
+ */
+ data class LikeTangemApp(val onClick: () -> Unit) : WalletNotification(
+ state = NotificationState.Action(
+ title = "Like Tangem App?",
+ iconResId = R.drawable.ic_star_24,
+ onClick = onClick,
+ tint = TangemColorPalette.Tangerine,
+ ),
+ )
+
+ /**
+ * "Scan the card" notification
+ *
+ * @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",
+ iconResId = R.drawable.ic_tangem_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 8142667ad3..a10b91ab37 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
@@ -3,28 +3,63 @@ package com.tangem.feature.wallet.presentation.wallet.state
import kotlinx.collections.immutable.ImmutableList
/**
- * Wallet state holder
+ * Wallet screen state holder
*
- * @property onBackClick lambda be invoked when back button is clicked
- * @property headerConfig header config
+ * @property onBackClick lambda be invoked when back button is clicked
+ * @property topBarConfig top bar config
+ * @property selectedWallet selected wallet
+ * @property wallets list of wallets states
+ * @property contentItems content items
+ * @property notifications notifications
*
[REDACTED_AUTHOR]
*/
-internal data class WalletStateHolder(
- val onBackClick: () -> Unit,
- val headerConfig: HeaderConfig,
+internal sealed class WalletStateHolder(
+ open val onBackClick: () -> Unit,
+ open val topBarConfig: WalletTopBarConfig,
+ open val selectedWallet: WalletCardState,
+ open val wallets: ImmutableList,
+ open val contentItems: ImmutableList,
+ open val notifications: ImmutableList,
) {
/**
- * Header config
+ * Multi currency wallet content state
*
- * @property wallets list of wallets states
- * @property onScanCardClick lambda be invoked when scan card button is clicked
- * @property onMoreClick lambda be invoked when more button is clicked
+ * @property onBackClick lambda be invoked when back button is clicked
+ * @property topBarConfig top bar config
+ * @property selectedWallet selected wallet
+ * @property wallets list of wallets states
+ * @property contentItems content items
+ * @property notifications notifications
+ * @property onOrganizeTokensClick lambda be invoked when organize tokens button is clicked
*/
- data class HeaderConfig(
- val wallets: ImmutableList,
- val onScanCardClick: () -> Unit,
- val onMoreClick: () -> Unit,
- )
+ data class MultiCurrencyContent(
+ override val onBackClick: () -> Unit,
+ override val topBarConfig: WalletTopBarConfig,
+ override val selectedWallet: WalletCardState,
+ override val wallets: ImmutableList,
+ override val contentItems: ImmutableList,
+ override val notifications: ImmutableList,
+ val onOrganizeTokensClick: () -> Unit,
+ ) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
+
+ /**
+ * Single currency wallet content state
+ *
+ * @property onBackClick lambda be invoked when back button is clicked
+ * @property topBarConfig top bar config
+ * @property selectedWallet selected wallet
+ * @property wallets list of wallets states
+ * @property contentItems content items
+ * @property notifications notifications
+ */
+ data class SingleCurrencyContent(
+ override val onBackClick: () -> Unit,
+ override val topBarConfig: WalletTopBarConfig,
+ override val selectedWallet: WalletCardState,
+ override val wallets: ImmutableList,
+ override val contentItems: ImmutableList,
+ override val notifications: ImmutableList,
+ ) : WalletStateHolder(onBackClick, topBarConfig, selectedWallet, wallets, contentItems, notifications)
}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletTopBarConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletTopBarConfig.kt
new file mode 100644
index 0000000000..f1838ea250
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletTopBarConfig.kt
@@ -0,0 +1,9 @@
+package com.tangem.feature.wallet.presentation.wallet.state
+
+/**
+ * Wallet screen top bar config
+ *
+ * @property onScanCardClick lambda be invoked when scan card button is clicked
+ * @property onMoreClick lambda be invoked when more button is clicked
+ */
+data class WalletTopBarConfig(val onScanCardClick: () -> Unit, val onMoreClick: () -> Unit)
\ 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 ba17e6e692..b5cf68559c 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
@@ -1,10 +1,38 @@
package com.tangem.feature.wallet.presentation.wallet.ui
import androidx.activity.compose.BackHandler
-import androidx.compose.material.Scaffold
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import com.tangem.core.ui.components.RoundedActionButton
+import com.tangem.core.ui.components.transactions.Transaction
+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.component.NetworkGroupItem
+import com.tangem.feature.wallet.presentation.common.component.TokenItem
+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.WalletHeader
+import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletCardsList
+import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletTopBar
/**
* Wallet screen
@@ -14,12 +42,180 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletHeader
[REDACTED_AUTHOR]
*/
@Composable
-internal fun WalletScreen(state: WalletStateHolder) {
+internal fun WalletScreen(state: WalletStateHolder, modifier: Modifier = Modifier) {
BackHandler(onBack = state.onBackClick)
Scaffold(
- topBar = { WalletHeader(config = state.headerConfig) },
- ) {
- // TODO: [REDACTED_TASK_KEY] Design a body with tokens and transactions
+ topBar = { WalletTopBar(config = state.topBarConfig) },
+ containerColor = TangemTheme.colors.background.secondary,
+ ) { scaffoldPaddings ->
+ val lastContentItemIndex = remember(state.contentItems) { state.contentItems.lastIndex }
+
+ LazyColumn(
+ modifier = modifier
+ .padding(scaffoldPaddings)
+ .fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ item { WalletCardsList(wallets = state.wallets) }
+
+ itemsIndexed(
+ items = state.contentItems,
+ key = { index, item ->
+ when (item) {
+ is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> item.networkName
+ is WalletContentItemState.MultiCurrencyItem.Token -> item.state.id
+ is WalletContentItemState.SingleCurrencyItem.Title -> index
+ is WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle -> item.title
+ is WalletContentItemState.SingleCurrencyItem.Transaction -> index
+ }
+ },
+ ) { index, item ->
+ val itemModifier = Modifier
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .clipFirstAndLastItems(index, lastContentItemIndex)
+
+ when (item) {
+ is WalletContentItemState.MultiCurrencyItem.NetworkGroupTitle -> {
+ NetworkGroupItem(networkName = item.networkName, modifier = itemModifier)
+ }
+ is WalletContentItemState.MultiCurrencyItem.Token -> {
+ TokenItem(state = item.state, modifier = itemModifier)
+ }
+ is WalletContentItemState.SingleCurrencyItem.Title -> {
+ SingleCurrencyTitle(config = item, modifier = itemModifier)
+ }
+ is WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle -> {
+ TransactionGroupTitle(config = item, modifier = itemModifier)
+ }
+ is WalletContentItemState.SingleCurrencyItem.Transaction -> {
+ Transaction(state = item.state, modifier = itemModifier)
+ }
+ }
+ }
+
+ if (state is WalletStateHolder.MultiCurrencyContent) {
+ item {
+ RoundedActionButton(
+ text = stringResource(id = R.string.organize_tokens_title),
+ iconResId = R.drawable.ic_filter_24,
+ onClick = state.onOrganizeTokensClick,
+ modifier = Modifier.padding(top = TangemTheme.dimens.spacing14),
+ )
+ }
+ }
+ }
}
-}
\ No newline at end of file
+}
+
+@Composable
+private fun SingleCurrencyTitle(
+ config: WalletContentItemState.SingleCurrencyItem.Title,
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier
+ .background(TangemTheme.colors.background.primary)
+ .fillMaxWidth()
+ .padding(top = TangemTheme.dimens.spacing12)
+ .padding(horizontal = TangemTheme.dimens.spacing16),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Text(
+ text = stringResource(id = R.string.common_transactions),
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.subtitle2,
+ )
+
+ Row(
+ modifier = Modifier.clickable(onClick = config.onExploreClick),
+ horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_compass_24),
+ contentDescription = null,
+ modifier = Modifier.size(size = TangemTheme.dimens.size18),
+ tint = TangemTheme.colors.icon.informative,
+ )
+ Text(
+ text = stringResource(id = R.string.common_explorer),
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.subtitle2,
+ )
+ }
+ }
+}
+
+@Composable
+private fun TransactionGroupTitle(
+ config: WalletContentItemState.SingleCurrencyItem.TransactionGroupTitle,
+ modifier: Modifier = Modifier,
+) {
+ Text(
+ text = config.title,
+ modifier = modifier
+ .background(TangemTheme.colors.background.primary)
+ .fillMaxWidth()
+ .padding(
+ horizontal = TangemTheme.dimens.spacing16,
+ vertical = TangemTheme.dimens.spacing14,
+ ),
+ color = TangemTheme.colors.text.tertiary,
+ textAlign = TextAlign.Start,
+ style = TangemTheme.typography.body2,
+ )
+}
+
+private fun Modifier.clipFirstAndLastItems(index: Int, lastItemIndex: Int): Modifier = composed {
+ when (index) {
+ 0 -> {
+ this
+ .padding(top = TangemTheme.dimens.spacing14)
+ .clip(
+ RoundedCornerShape(
+ topStart = TangemTheme.dimens.radius16,
+ topEnd = TangemTheme.dimens.radius16,
+ ),
+ )
+ }
+ lastItemIndex -> {
+ this
+ .clip(
+ RoundedCornerShape(
+ bottomStart = TangemTheme.dimens.radius16,
+ bottomEnd = TangemTheme.dimens.radius16,
+ ),
+ )
+ }
+ else -> this
+ }
+}
+
+// region Preview
+@Preview(showBackground = true, widthDp = 360)
+@Composable
+private fun WalletScreenPreview_Light(
+ @PreviewParameter(WalletScreenParameterProvider::class) state: WalletStateHolder,
+) {
+ TangemTheme {
+ WalletScreen(state)
+ }
+}
+
+@Preview(showBackground = true, widthDp = 360)
+@Composable
+private fun WalletScreenPreview_Dark(
+ @PreviewParameter(WalletScreenParameterProvider::class) state: WalletStateHolder,
+) {
+ TangemTheme(isDark = true) {
+ WalletScreen(state)
+ }
+}
+
+private class WalletScreenParameterProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ WalletPreviewData.multicurrencyWalletScreenState,
+ WalletPreviewData.singleWalletScreenState,
+ ),
+)
+// endregion Preview
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt
index 562c954023..d92651ac6f 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt
@@ -44,7 +44,7 @@ internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) {
ConstraintLayout(
modifier = Modifier
.fillMaxWidth()
- .padding(horizontal = TangemTheme.dimens.spacing12),
+ .padding(horizontal = TangemTheme.dimens.spacing14),
) {
val (balanceBlock, imageItem) = createRefs()
Column(
@@ -182,10 +182,10 @@ private fun Preview_WalletCard_DarkTheme(@PreviewParameter(WalletCardStateProvid
private class WalletCardStateProvider : CollectionPreviewParameterProvider(
collection = listOf(
- WalletPreviewData.walletCardContent,
- WalletPreviewData.walletCardLoading,
- WalletPreviewData.walletCardHiddenContent,
- WalletPreviewData.walletCardError,
+ WalletPreviewData.walletCardContentState,
+ WalletPreviewData.walletCardLoadingState,
+ WalletPreviewData.walletCardHiddenContentState,
+ WalletPreviewData.walletCardErrorState,
),
)
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCardsList.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCardsList.kt
new file mode 100644
index 0000000000..d37d359252
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCardsList.kt
@@ -0,0 +1,78 @@
+package com.tangem.feature.wallet.presentation.wallet.ui.components
+
+import androidx.compose.animation.core.*
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+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 kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+
+/**
+ * Wallets list
+ *
+ * @param wallets list of wallet state
+ *
+[REDACTED_AUTHOR]
+ */
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+internal fun WalletCardsList(wallets: ImmutableList) {
+ val horizontalCardPadding = TangemTheme.dimens.spacing16
+ val itemWidth = LocalConfiguration.current.screenWidthDp.dp - horizontalCardPadding * 2
+
+ val lazyListState = rememberLazyListState()
+ LazyRow(
+ modifier = Modifier.background(color = TangemTheme.colors.background.secondary),
+ state = lazyListState,
+ contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
+ horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
+ flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState),
+ ) {
+ items(items = wallets, key = WalletCardState::id) { state ->
+ WalletCard(state = state, modifier = Modifier.width(itemWidth))
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun Preview_WalletHeader_LightTheme() {
+ TangemTheme(isDark = false) {
+ WalletCardsList(
+ wallets = persistentListOf(
+ WalletPreviewData.walletCardContentState,
+ WalletPreviewData.walletCardLoadingState,
+ WalletPreviewData.walletCardHiddenContentState,
+ WalletPreviewData.walletCardErrorState,
+ ),
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun Preview_WalletHeader_DarkTheme() {
+ TangemTheme(isDark = true) {
+ WalletCardsList(
+ wallets = persistentListOf(
+ WalletPreviewData.walletCardContentState,
+ WalletPreviewData.walletCardLoadingState,
+ WalletPreviewData.walletCardHiddenContentState,
+ WalletPreviewData.walletCardErrorState,
+ ),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt
deleted file mode 100644
index cb01b48377..0000000000
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.tangem.feature.wallet.presentation.wallet.ui.components
-
-import androidx.compose.animation.core.*
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.LocalOverscrollConfiguration
-import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.res.painterResource
-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.res.TangemTheme
-import com.tangem.feature.wallet.impl.R
-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.WalletStateHolder
-import kotlinx.collections.immutable.persistentListOf
-
-/**
- * Wallet screen header
- *
- * @param config config
- *
-[REDACTED_AUTHOR]
- */
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-internal fun WalletHeader(config: WalletStateHolder.HeaderConfig) {
- Column(
- modifier = Modifier
- .background(color = TangemTheme.colors.background.secondary)
- .padding(bottom = TangemTheme.dimens.spacing14),
- ) {
- TopBar(onScanCardClick = config.onScanCardClick, onMoreClick = config.onMoreClick)
-
- CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
- val lazyListState = rememberLazyListState()
- LazyRow(
- state = lazyListState,
- contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing16),
- horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
- flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState),
- ) {
- items(items = config.wallets, key = WalletCardState::id) { state ->
- WalletCard(
- state = state,
- modifier = Modifier.width(
- LocalConfiguration.current.screenWidthDp.dp - TangemTheme.dimens.size32,
- ),
- )
- }
- }
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-private fun TopBar(onScanCardClick: () -> Unit, onMoreClick: () -> Unit) {
- TopAppBar(
- title = {
- Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null)
- },
- actions = {
- IconButton(onClick = onScanCardClick) {
- Icon(painter = painterResource(id = R.drawable.ic_tap_card_24), contentDescription = "Scan card")
- }
- IconButton(onClick = onMoreClick) {
- Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = "More")
- }
- },
- colors = TopAppBarDefaults.topAppBarColors(
- containerColor = TangemTheme.colors.background.secondary,
- titleContentColor = TangemTheme.colors.icon.primary1,
- actionIconContentColor = TangemTheme.colors.icon.primary1,
- ),
- )
-}
-
-@Preview
-@Composable
-private fun Preview_WalletHeader_LightTheme(
- @PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig,
-) {
- TangemTheme(isDark = false) {
- WalletHeader(state)
- }
-}
-
-@Preview
-@Composable
-private fun Preview_WalletHeader_DarkTheme(
- @PreviewParameter(WalletHeaderProvider::class) state: WalletStateHolder.HeaderConfig,
-) {
- TangemTheme(isDark = true) {
- WalletHeader(state)
- }
-}
-
-private class WalletHeaderProvider : CollectionPreviewParameterProvider(
- collection = listOf(
- WalletStateHolder.HeaderConfig(
- wallets = persistentListOf(
- WalletPreviewData.walletCardContent,
- WalletPreviewData.walletCardLoading,
- WalletPreviewData.walletCardHiddenContent,
- WalletPreviewData.walletCardError,
- ),
- onScanCardClick = {},
- onMoreClick = {},
- ),
- ),
-)
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletTopBar.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletTopBar.kt
new file mode 100644
index 0000000000..54db11db4d
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletTopBar.kt
@@ -0,0 +1,55 @@
+package com.tangem.feature.wallet.presentation.wallet.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.wallet.impl.R
+import com.tangem.feature.wallet.presentation.common.WalletPreviewData
+import com.tangem.feature.wallet.presentation.wallet.state.WalletTopBarConfig
+
+/**
+ * Wallet screen top bar
+ *
+ * @param config top bar config
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun WalletTopBar(config: WalletTopBarConfig) {
+ TopAppBar(
+ title = {
+ Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null)
+ },
+ actions = {
+ IconButton(onClick = config.onScanCardClick) {
+ Icon(painter = painterResource(id = R.drawable.ic_tap_card_24), contentDescription = "Scan card")
+ }
+ IconButton(onClick = config.onMoreClick) {
+ Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), 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_WalletTopBar_LightTheme() {
+ TangemTheme(isDark = false) {
+ WalletTopBar(config = WalletPreviewData.walletTopBarConfig)
+ }
+}
+
+@Preview
+@Composable
+private fun Preview_WalletTopBar_DarkTheme() {
+ TangemTheme(isDark = true) {
+ WalletTopBar(config = WalletPreviewData.walletTopBarConfig)
+ }
+}
\ 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 a565bb8c91..dd643d7352 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
@@ -27,9 +27,9 @@ internal class WalletViewModel @Inject constructor() : ViewModel() {
private set
// TODO: [REDACTED_TASK_KEY] Use production data instead of WalletPreviewData
- private fun getInitialState(): WalletStateHolder = WalletPreviewData.walletScreenState.copy(
+ private fun getInitialState(): WalletStateHolder = WalletPreviewData.multicurrencyWalletScreenState.copy(
onBackClick = { router.popBackStack() },
- headerConfig = WalletPreviewData.walletScreenState.headerConfig.copy(
+ topBarConfig = WalletPreviewData.multicurrencyWalletScreenState.topBarConfig.copy(
onScanCardClick = { router.openOrganizeTokensScreen() },
),
)
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index 1eda166c12..8b3d5af5bd 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -31,6 +31,7 @@ compose-constraint = "1.0.1"
compose-navigation = "2.5.3"
compose-accompanist = "0.30.1"
compose-paging = "1.0.0-alpha18"
+compose-reorderable = "0.9.6"
# endregion Compose
# region Other libraries
@@ -147,6 +148,7 @@ compose-accompanist-appCompatTheme = { module = "com.google.accompanist:accompan
compose-accompanist-systemUiController = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "compose-accompanist" }
compose-accompanist-webView = { module = "com.google.accompanist:accompanist-webview", version.ref = "compose-accompanist" }
compose-paging = { module = "androidx.paging:paging-compose", version.ref = "compose-paging" }
+compose-reorderable = { module = "org.burnoutcrew.composereorderable:reorderable", version.ref = "compose-reorderable" }
# endregion Compose
# region Firebase
diff --git a/plugins/configuration/detekt.yml b/plugins/configuration/detekt.yml
deleted file mode 100644
index 51dbfc0927..0000000000
--- a/plugins/configuration/detekt.yml
+++ /dev/null
@@ -1,836 +0,0 @@
-build:
- maxIssues: 0
-
-config:
- validation: true
- warningsAsErrors: false
-
-processors:
- active: true
-
-console-reports:
- active: true
- exclude:
- - 'ProjectStatisticsReport'
- - 'ComplexityReport'
- - 'NotificationReport'
- - 'FindingsReport'
- - 'FileBasedFindingsReport'
-# - 'LiteFindingsReport'
-
-output-reports:
- active: true
- exclude:
- - 'HtmlOutputReport'
- # - 'TxtOutputReport'
- - 'XmlOutputReport'
- - 'SarifOutputReport'
- - 'MdOutputReport'
-
-comments:
- active: false
- AbsentOrWrongFileLicense:
- active: false
- licenseTemplateFile: 'license.template'
- licenseTemplateIsRegex: false
- CommentOverPrivateFunction:
- active: false
- CommentOverPrivateProperty:
- active: false
- DeprecatedBlockTag:
- active: false
- EndOfSentenceFormat:
- active: false
- endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
- KDocReferencesNonPublicProperty:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- OutdatedDocumentation:
- active: false
- matchTypeParameters: true
- matchDeclarationsOrder: true
- allowParamOnConstructorProperties: false
- UndocumentedPublicClass:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- searchInNestedClass: true
- searchInInnerClass: true
- searchInInnerObject: true
- searchInInnerInterface: true
- UndocumentedPublicFunction:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- UndocumentedPublicProperty:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
-
-complexity:
- active: true
- CyclomaticComplexMethod:
- active: true
- ComplexCondition:
- active: true
- threshold: 4
- ComplexInterface:
- active: false
- threshold: 10
- includeStaticDeclarations: false
- includePrivateDeclarations: false
- LabeledExpression:
- active: false
- ignoredLabels: [ ]
- LargeClass:
- active: true
- threshold: 300
- LongMethod:
- active: true
- threshold: 70
- LongParameterList:
- active: true
- functionThreshold: 6
- constructorThreshold: 7
- ignoreDefaultParameters: true
- ignoreDataClasses: true
- ignoreAnnotated: [ 'Provides' ]
- MethodOverloading:
- active: true
- threshold: 6
- NamedArguments:
- active: true
- threshold: 3
- ignoreArgumentsMatchingNames: false
- NestedBlockDepth:
- active: true
- threshold: 5
- NestedScopeFunctions:
- active: false
- threshold: 1
- functions:
- - 'kotlin.apply'
- - 'kotlin.run'
- - 'kotlin.with'
- - 'kotlin.let'
- - 'kotlin.also'
- ReplaceSafeCallChainWithRun:
- active: true
- StringLiteralDuplication:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- threshold: 3
- ignoreAnnotation: true
- excludeStringsWithLessThan5Characters: true
- ignoreStringsRegex: '$^'
- TooManyFunctions:
- active: true
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- thresholdInFiles: 20
- thresholdInClasses: 20
- thresholdInInterfaces: 20
- thresholdInObjects: 20
- thresholdInEnums: 20
- ignoreDeprecated: false
- ignorePrivate: true
- ignoreOverridden: false
-
-coroutines:
- active: true
- GlobalCoroutineUsage:
- active: true
- InjectDispatcher:
- active: false #TODO
- dispatcherNames:
- - 'IO'
- - 'Default'
- - 'Unconfined'
- RedundantSuspendModifier:
- active: true
- SleepInsteadOfDelay:
- active: true
- SuspendFunWithCoroutineScopeReceiver:
- active: false
- SuspendFunWithFlowReturnType:
- active: true
-
-empty-blocks:
- active: true
- EmptyCatchBlock:
- active: true
- allowedExceptionNameRegex: '_|(ignore|expected).*'
- EmptyClassBlock:
- active: true
- EmptyDefaultConstructor:
- active: true
- EmptyDoWhileBlock:
- active: true
- EmptyElseBlock:
- active: true
- EmptyFinallyBlock:
- active: true
- EmptyForBlock:
- active: true
- EmptyFunctionBlock:
- active: true
- ignoreOverridden: true
- EmptyIfBlock:
- active: true
- EmptyInitBlock:
- active: true
- EmptyKtFile:
- active: true
- EmptySecondaryConstructor:
- active: true
- EmptyTryBlock:
- active: true
- EmptyWhenBlock:
- active: true
- EmptyWhileBlock:
- active: true
-
-exceptions:
- active: true
- ExceptionRaisedInUnexpectedLocation:
- active: true
- methodNames:
- - 'equals'
- - 'finalize'
- - 'hashCode'
- - 'toString'
- InstanceOfCheckForException:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- NotImplementedDeclaration:
- active: false
- ObjectExtendsThrowable:
- active: true
- PrintStackTrace:
- active: true
- RethrowCaughtException:
- active: true
- ReturnFromFinally:
- active: true
- ignoreLabeled: false
- SwallowedException:
- active: false
- ignoredExceptionTypes:
- - 'InterruptedException'
- - 'MalformedURLException'
- - 'NumberFormatException'
- - 'ParseException'
- allowedExceptionNameRegex: '_|(ignore|expected).*'
- ThrowingExceptionFromFinally:
- active: true
- ThrowingExceptionInMain:
- active: false
- ThrowingExceptionsWithoutMessageOrCause:
- active: true
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- exceptions:
- - 'ArrayIndexOutOfBoundsException'
- - 'Exception'
- - 'IllegalArgumentException'
- - 'IllegalMonitorStateException'
- - 'IllegalStateException'
- - 'IndexOutOfBoundsException'
- - 'NullPointerException'
- - 'RuntimeException'
- - 'Throwable'
- ThrowingNewInstanceOfSameException:
- active: true
- TooGenericExceptionCaught:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- exceptionNames:
- - 'ArrayIndexOutOfBoundsException'
- - 'Error'
- - 'Exception'
- - 'IllegalMonitorStateException'
- - 'IndexOutOfBoundsException'
- - 'NullPointerException'
- - 'RuntimeException'
- - 'Throwable'
- allowedExceptionNameRegex: '_|(ignore|expected).*'
- TooGenericExceptionThrown:
- active: true
- exceptionNames:
- - 'Error'
- - 'Exception'
- - 'RuntimeException'
- - 'Throwable'
-
-naming:
- active: true
- BooleanPropertyNaming:
- active: true
- allowedPattern: '^(is|has|are)'
- ignoreOverridden: true
- ClassNaming:
- active: true
- classPattern: '[A-Z][a-zA-Z0-9]*'
- ConstructorParameterNaming:
- active: true
- parameterPattern: '[a-z][A-Za-z0-9]*'
- privateParameterPattern: '[a-z][A-Za-z0-9]*'
- excludeClassPattern: '$^'
- ignoreOverridden: true
- EnumNaming:
- active: true
- enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
- ForbiddenClassName:
- active: false
- forbiddenName: [ ]
- FunctionMaxLength:
- active: false
- maximumFunctionNameLength: 30
- FunctionMinLength:
- active: false
- minimumFunctionNameLength: 3
- FunctionNaming:
- active: true
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- functionPattern: '[a-z][a-zA-Z0-9]*'
- excludeClassPattern: '$^'
- ignoreOverridden: true
- ignoreAnnotated: [ 'Composable' ]
- FunctionParameterNaming:
- active: true
- parameterPattern: '[a-z][A-Za-z0-9]*'
- excludeClassPattern: '$^'
- ignoreOverridden: true
- InvalidPackageDeclaration:
- active: true
- rootPackage: ''
- requireRootInDeclaration: false
- LambdaParameterNaming:
- active: false
- parameterPattern: '[a-z][A-Za-z0-9]*|_'
- MatchingDeclarationName: # Same as FileName
- active: false
- mustBeFirst: true
- MemberNameEqualsClassName:
- active: false
- ignoreOverridden: true
- NoNameShadowing:
- active: true
- NonBooleanPropertyPrefixedWithIs:
- active: true
- ObjectPropertyNaming:
- active: true
- constantPattern: '[A-Za-z][_A-Za-z0-9]*'
- propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
- privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
- PackageNaming:
- active: true
- packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
- TopLevelPropertyNaming:
- active: true
- constantPattern: '[A-Z][_A-Z0-9]*'
- propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
- privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
- VariableMaxLength:
- active: false
- maximumVariableNameLength: 50
- VariableMinLength:
- active: false
- minimumVariableNameLength: 3
- VariableNaming:
- active: true
- variablePattern: '[a-z][A-Za-z0-9]*'
- privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
- excludeClassPattern: '$^'
- ignoreOverridden: true
-
-performance:
- active: true
- ArrayPrimitive:
- active: true
- CouldBeSequence:
- active: false
- threshold: 3
- ForEachOnRange:
- active: true
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- SpreadOperator:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- UnnecessaryTemporaryInstantiation:
- active: true
-
-potential-bugs:
- active: true
- AvoidReferentialEquality:
- active: true
- forbiddenTypePatterns:
- - 'kotlin.String'
- CastToNullableType:
- active: true
- Deprecation:
- active: false
- DontDowncastCollectionTypes:
- active: true
- DoubleMutabilityForCollection:
- active: true
- mutableTypes:
- - 'kotlin.collections.MutableList'
- - 'kotlin.collections.MutableMap'
- - 'kotlin.collections.MutableSet'
- - 'java.util.ArrayList'
- - 'java.util.LinkedHashSet'
- - 'java.util.HashSet'
- - 'java.util.LinkedHashMap'
- - 'java.util.HashMap'
- ElseCaseInsteadOfExhaustiveWhen:
- active: false
- EqualsAlwaysReturnsTrueOrFalse:
- active: true
- EqualsWithHashCodeExist:
- active: true
- ExitOutsideMain:
- active: false
- ExplicitGarbageCollectionCall:
- active: true
- HasPlatformType:
- active: true
- IgnoredReturnValue:
- active: true
- returnValueAnnotations:
- - '*.CheckResult'
- - '*.CheckReturnValue'
- ignoreReturnValueAnnotations:
- - '*.CanIgnoreReturnValue'
- ignoreFunctionCall: [ ]
- ImplicitDefaultLocale:
- active: true
- ImplicitUnitReturnType:
- active: false
- allowExplicitReturnType: true
- InvalidRange:
- active: true
- IteratorHasNextCallsNextMethod:
- active: true
- IteratorNotThrowingNoSuchElementException:
- active: true
- LateinitUsage:
- active: false
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- ignoreAnnotated: [ 'Inject' ]
- ignoreOnClassesPattern: ''
- MapGetWithNotNullAssertionOperator:
- active: true
- MissingPackageDeclaration:
- active: true
- excludes: [ '**/*.kts' ]
- NullCheckOnMutableProperty:
- active: true
- NullableToStringCall:
- active: true
- UnconditionalJumpStatementInLoop:
- active: true
- UnnecessaryNotNullOperator:
- active: true
- UnnecessarySafeCall:
- active: true
- UnreachableCatchBlock:
- active: true
- UnreachableCode:
- active: true
- UnsafeCallOnNullableType:
- active: true
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**' ]
- UnsafeCast:
- active: true
- UnusedUnaryOperator:
- active: true
- UselessPostfixExpression:
- active: true
- WrongEqualsTypeParameter:
- active: true
-
-style:
- active: true
- CanBeNonNullable:
- active: false
- CascadingCallWrapping:
- active: false
- includeElvis: true
- ClassOrdering:
- active: true
- CollapsibleIfStatements:
- active: true
- DataClassContainsFunctions:
- active: false
- conversionFunctionPrefix:
- - 'to'
- DataClassShouldBeImmutable:
- active: false
- DestructuringDeclarationWithTooManyEntries:
- active: true
- maxDestructuringEntries: 3
- EqualsNullCall:
- active: false
- EqualsOnSignatureLine:
- active: true
- ExplicitCollectionElementAccessMethod:
- active: true
- ExplicitItLambdaParameter:
- active: true
- ExpressionBodySyntax:
- active: false
- includeLineWrapping: true
- ForbiddenComment:
- active: false
- values:
- - 'FIXME:'
- - 'STOPSHIP:'
- - 'TODO:'
- allowedPatterns: ''
- customMessage: ''
- ForbiddenImport:
- active: false
- imports: [ ]
- forbiddenPatterns: ''
- ForbiddenMethodCall:
- active: false
- methods:
- - reason: 'print does not allow you to configure the output stream. Use a logger instead.'
- value: 'kotlin.io.print'
- - reason: 'println does not allow you to configure the output stream. Use a logger instead.'
- value: 'kotlin.io.println'
- ForbiddenSuppress:
- active: false
- rules: [ ]
- ForbiddenVoid:
- active: false
- ignoreOverridden: false
- ignoreUsageInGenerics: false
- FunctionOnlyReturningConstant:
- active: true
- ignoreOverridableFunction: true
- ignoreActualFunction: true
- LoopWithTooManyJumpStatements:
- active: true
- maxJumpCount: 2
- MagicNumber:
- active: true
- excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/*.kts' ]
- ignoreNumbers:
- - '-1'
- - '0'
- - '1'
- - '2'
- ignoreHashCodeFunction: true
- ignorePropertyDeclaration: true
- ignoreLocalVariableDeclaration: false
- ignoreConstantDeclaration: true
- ignoreCompanionObjectPropertyDeclaration: true
- ignoreAnnotation: false
- ignoreNamedArgument: true
- ignoreEnums: false
- ignoreRanges: false
- ignoreExtensionFunctions: true
- ignoreAnnotated: [ 'Preview' ]
- MandatoryBracesIfStatements:
- active: true
- MandatoryBracesLoops:
- active: true
- MaxChainedCallsOnSameLine:
- active: true
- maxChainedCalls: 5
- MaxLineLength:
- active: false # Same as MaximumLineLength
- maxLineLength: 120
- excludePackageStatements: true
- excludeImportStatements: true
- excludeCommentStatements: false
- MayBeConst:
- active: true
- ModifierOrder:
- active: false # Same as ModifierOrdering
- MultilineLambdaItParameter:
- active: true
- NestedClassesVisibility:
- active: true
- NewLineAtEndOfFile:
- active: false # Same as FinalNewline
- NoTabs:
- active: true
- NullableBooleanCheck:
- active: true
- ObjectLiteralToLambda:
- active: true
- OptionalAbstractKeyword:
- active: true
- OptionalUnit:
- active: true
- OptionalWhenBraces:
- active: false
- PreferToOverPairSyntax:
- active: false
- ProtectedMemberInFinalClass:
- active: true
- RedundantExplicitType:
- active: false
- RedundantHigherOrderMapUsage:
- active: true
- RedundantVisibilityModifierRule:
- active: true
- ReturnCount:
- active: false
- max: 2
- excludedFunctions:
- - 'equals'
- excludeLabeled: false
- excludeReturnFromLambda: true
- excludeGuardClauses: false
- SafeCast:
- active: true
- SerialVersionUIDInSerializableClass:
- active: true
- SpacingBetweenPackageAndImports:
- active: true
- ThrowsCount:
- active: false
- max: 2
- excludeGuardClauses: false
- TrailingWhitespace:
- active: false
- UnderscoresInNumericLiterals:
- active: false
- acceptableLength: 4
- allowNonStandardGrouping: false
- UnnecessaryAbstractClass:
- active: true
- UnnecessaryAnnotationUseSiteTarget:
- active: false
- UnnecessaryApply:
- active: true
- UnnecessaryBackticks:
- active: true
- UnnecessaryFilter:
- active: true
- UnnecessaryInheritance:
- active: true
- UnnecessaryInnerClass:
- active: false
- UnnecessaryLet:
- active: true
- UnnecessaryParentheses:
- active: true
- UntilInsteadOfRangeTo:
- active: true
- UnusedImports:
- active: true
- UnusedPrivateClass:
- active: true
- ignoreAnnotated: [ 'UnusedRequiredComponent' ]
- UnusedPrivateMember:
- active: true
- allowedNames: '(_|ignored|expected|serialVersionUID)'
- ignoreAnnotated: [ 'Preview', 'UnusedRequiredComponent' ]
- UseAnyOrNoneInsteadOfFind:
- active: true
- UseArrayLiteralsInAnnotations:
- active: true
- UseCheckNotNull:
- active: true
- UseCheckOrError:
- active: true
- UseDataClass:
- active: false
- allowVars: false
- UseEmptyCounterpart:
- active: true
- UseIfEmptyOrIfBlank:
- active: true
- UseIfInsteadOfWhen:
- active: false
- UseIsNullOrEmpty:
- active: true
- UseOrEmpty:
- active: true
- UseRequire:
- active: true
- UseRequireNotNull:
- active: true
- UselessCallOnNotNull:
- active: true
- UtilityClassWithPublicConstructor:
- active: true
- VarCouldBeVal:
- active: true
- ignoreLateinitVar: false
- WildcardImport:
- active: false
- excludeImports:
- - 'java.util.*'
-
-formatting:
- active: true
- android: true
- AnnotationOnSeparateLine:
- active: false
- AnnotationSpacing:
- active: true
- ArgumentListWrapping:
- active: true
- indentSize: 4
- maxLineLength: 120
- BlockCommentInitialStarAlignment:
- active: true
- ChainWrapping:
- active: true
- CommentSpacing:
- active: true
- CommentWrapping:
- active: false
- indentSize: 4
- DiscouragedCommentLocation:
- active: true
- EnumEntryNameCase:
- active: true
- Filename:
- active: true # This rules overlaps with naming>MatchingDeclarationName from the standard rules
- mustBeFirst: true
- FinalNewline: # This rules overlaps with style>NewLineAtEndOfFile from the standard rules
- active: true
- insertFinalNewLine: true
- FunKeywordSpacing:
- active: true
- FunctionReturnTypeSpacing:
- active: true
- FunctionSignature:
- active: true
- forceMultilineWhenParameterCountGreaterOrEqualThan: 2147483647
- functionBodyExpressionWrapping: 'default'
- maxLineLength: 120
- indentSize: 4
- FunctionStartOfBodySpacing:
- active: true
- FunctionTypeReferenceSpacing:
- active: true
- ImportOrdering:
- active: false
- layout: 'java.**,javax.**,kotlin.**,kotlinx.**,android.**,androidx.**,*,^'
- Indentation:
- active: true
- indentSize: 4
- KdocWrapping:
- active: true
- indentSize: 4
- MaximumLineLength:
- excludes: [ "**/assets/**" ]
- active: true # This rules overlaps with style>MaxLineLength from the standard rules
- maxLineLength: 120
- excludePackageStatements: true
- excludeImportStatements: true
- excludeCommentStatements: false
- ModifierListSpacing:
- active: true
- ModifierOrdering:
- active: true # This rules overlaps with style>ModifierOrder from the standard rules
- MultiLineIfElse:
- active: true
- NoBlankLineBeforeRbrace:
- active: true
- NoBlankLinesInChainedMethodCalls:
- active: true
- NoConsecutiveBlankLines:
- active: true
- NoEmptyClassBody:
- active: true
- NoEmptyFirstLineInMethodBlock:
- active: true
- NoLineBreakAfterElse:
- active: true
- NoLineBreakBeforeAssignment:
- active: true
- NoMultipleSpaces:
- active: true
- NoSemicolons:
- active: true
- NoTrailingSpaces:
- active: true
- NoUnitReturn:
- active: true
- NoUnusedImports:
- active: true
- NoWildcardImports:
- active: false
- packagesToUseImportOnDemandProperty: 'java.util.*,kotlinx.android.synthetic.**'
- NullableTypeSpacing:
- active: true
- PackageName:
- active: true
- ParameterListSpacing:
- active: true
- ParameterListWrapping:
- active: true
- maxLineLength: 120
- SpacingAroundAngleBrackets:
- active: true
- SpacingAroundColon:
- active: true
- SpacingAroundComma:
- active: true
- SpacingAroundCurly:
- active: true
- SpacingAroundDot:
- active: true
- SpacingAroundDoubleColon:
- active: true
- SpacingAroundKeyword:
- active: true
- SpacingAroundOperators:
- active: true
- SpacingAroundParens:
- active: true
- SpacingAroundRangeOperator:
- active: true
- SpacingAroundUnaryOperator:
- active: true
- SpacingBetweenDeclarationsWithAnnotations:
- active: true
- SpacingBetweenDeclarationsWithComments:
- active: false
- SpacingBetweenFunctionNameAndOpeningParenthesis:
- active: true
- StringTemplate:
- active: true
- TrailingCommaOnCallSite:
- active: true
- useTrailingCommaOnCallSite: true
- TrailingCommaOnDeclarationSite:
- active: true
- useTrailingCommaOnDeclarationSite: true
- TypeArgumentListSpacing:
- active: true
- TypeParameterListSpacing:
- active: true
- UnnecessaryParenthesesBeforeTrailingLambda:
- active: true
- Wrapping:
- active: true
- indentSize: 4
-
-compose:
- ReusedModifierInstance:
- active: true
- UnnecessaryEventHandlerParameter:
- active: true
- ComposableEventParameterNaming:
- active: true
- ComposableParametersOrdering:
- active: true
- ModifierDefaultValue:
- active: true
- MissingModifierDefaultValue:
- active: true
- ModifierHeightWithText:
- active: true
- ModifierParameterPosition:
- active: true
- PublicComposablePreview:
- active: true
- TopLevelComposableFunctions:
- active: true
- ComposeFunctionName:
- active: true
diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/DetektConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/DetektConfigurations.kt
index 1bcf190ab5..7f3919e6d8 100644
--- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/DetektConfigurations.kt
+++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/DetektConfigurations.kt
@@ -22,7 +22,7 @@ private fun DetektExtension.configure(project: Project) {
ignoreFailures = false
autoCorrect = true
buildUponDefaultConfig = true
- config.setFrom(project.rootProject.files("plugins/configuration/detekt.yml"))
+ config.setFrom(project.rootProject.files("tangem-android-tools/detekt-config.yml"))
}
private fun Project.configureDetektPlugins() {
diff --git a/tangem-android-tools b/tangem-android-tools
new file mode 160000
index 0000000000..03186c35c1
--- /dev/null
+++ b/tangem-android-tools
@@ -0,0 +1 @@
+Subproject commit 03186c35c13d8693d9a4c7dcb6e760fa58984e67