Updated on 2026-08-14
This commit is contained in:
commit
1f3c063223
38 changed files with 458 additions and 146 deletions
|
|
@ -11,15 +11,15 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object TransactionDomainModule {
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
@Singleton
|
||||
fun provideGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetFeeUseCase {
|
||||
return GetFeeUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
|
|
@ -28,7 +28,7 @@ internal object TransactionDomainModule {
|
|||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
@Singleton
|
||||
fun provideSendTransactionUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
transactionRepository: TransactionRepository,
|
||||
|
|
@ -43,7 +43,7 @@ internal object TransactionDomainModule {
|
|||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
@Singleton
|
||||
fun provideAssociateAssetUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
|
|
@ -59,20 +59,28 @@ internal object TransactionDomainModule {
|
|||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
@Singleton
|
||||
fun provideCreateTransactionUseCase(transactionRepository: TransactionRepository): CreateTransactionUseCase {
|
||||
return CreateTransactionUseCase(transactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
@Singleton
|
||||
fun provideIsFeeApproximateUseCase(feeRepository: FeeRepository): IsFeeApproximateUseCase {
|
||||
return IsFeeApproximateUseCase(feeRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ViewModelScoped
|
||||
@Singleton
|
||||
fun provideValidateTransactionUseCase(transactionRepository: TransactionRepository): ValidateTransactionUseCase {
|
||||
return ValidateTransactionUseCase(transactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsUtxoConsolidationAvailableUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): IsUtxoConsolidationAvailableUseCase {
|
||||
return IsUtxoConsolidationAvailableUseCase(walletManagersFacade)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,10 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
|
|
@ -126,11 +129,11 @@ internal object WalletsDomainModule {
|
|||
@Singleton
|
||||
fun providesValidateWalletAddressUseCase(
|
||||
walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): ValidateWalletAddressUseCase {
|
||||
return ValidateWalletAddressUseCase(
|
||||
walletAddressServiceRepository = walletAddressServiceRepository,
|
||||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.sdk.TangemSdkManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -54,4 +56,8 @@ class AppStateHolder @Inject constructor() : ReduxNavController, ReduxStateHolde
|
|||
override suspend fun onUserWalletSelected(userWallet: UserWallet) {
|
||||
mainStore?.onUserWalletSelected(userWallet)
|
||||
}
|
||||
|
||||
override fun sendFeedbackEmail() {
|
||||
mainStore?.dispatch(GlobalAction.SendEmail(FeedbackEmail()))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +1,39 @@
|
|||
package com.tangem.data.wallets
|
||||
package com.tangem.data.transaction
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import com.tangem.blockchain.blockchains.near.NearWalletManager
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.ParsedQrCode
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.errors.ParsedQrCodeErrors
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigInteger
|
||||
|
||||
class DefaultWalletAddressServiceRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletAddressServiceRepository {
|
||||
|
||||
override suspend fun validateAddress(userWalletId: UserWalletId, network: Network, address: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
override suspend fun validateAddress(userWalletId: UserWalletId, network: Network, address: String): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
|
||||
return if (blockchain.isNear()) {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
) ?: return false
|
||||
(walletManager as? NearWalletManager)?.validateAddress(address) ?: false
|
||||
} else {
|
||||
blockchain.validateAddress(address)
|
||||
if (blockchain.isNear()) {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
) ?: return@withContext false
|
||||
(walletManager as? NearWalletManager)?.validateAddress(address) ?: false
|
||||
} else {
|
||||
blockchain.validateAddress(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun validateMemo(network: Network, memo: String): Boolean {
|
||||
if (memo.isEmpty()) return true
|
||||
|
|
@ -2,9 +2,11 @@ package com.tangem.data.transaction.di
|
|||
|
||||
import com.tangem.data.transaction.DefaultFeeRepository
|
||||
import com.tangem.data.transaction.DefaultTransactionRepository
|
||||
import com.tangem.data.transaction.DefaultWalletAddressServiceRepository
|
||||
import com.tangem.datasource.local.walletmanager.WalletManagersStore
|
||||
import com.tangem.domain.transaction.FeeRepository
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -36,4 +38,16 @@ internal object TransactionDataModule {
|
|||
fun providesFeeRepository(): FeeRepository {
|
||||
return DefaultFeeRepository()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWalletAddressServiceRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): WalletAddressServiceRepository {
|
||||
return DefaultWalletAddressServiceRepository(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dispatchers = coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package com.tangem.data.wallets.di
|
||||
|
||||
import com.tangem.data.wallets.DefaultWalletNamesMigrationRepository
|
||||
import com.tangem.data.wallets.DefaultWalletAddressServiceRepository
|
||||
import com.tangem.data.wallets.DefaultWalletsRepository
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -24,14 +21,6 @@ internal object WalletsDataModule {
|
|||
return DefaultWalletsRepository(appPreferencesStore = appPreferencesStore)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWalletAddressServiceRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): WalletAddressServiceRepository {
|
||||
return DefaultWalletAddressServiceRepository(walletManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMigrateNamesRepository(appPreferencesStore: AppPreferencesStore): WalletNamesMigrationRepository {
|
||||
|
|
|
|||
|
|
@ -10,4 +10,6 @@ interface ReduxStateHolder {
|
|||
suspend fun dispatchWithMain(action: Action)
|
||||
|
||||
suspend fun onUserWalletSelected(userWallet: UserWallet)
|
||||
|
||||
fun sendFeedbackEmail()
|
||||
}
|
||||
|
|
@ -13,9 +13,9 @@ import com.tangem.blockchain.common.pagination.Page
|
|||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.common.trustlines.AssetRequirementsManager
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchain.extensions.SimpleResult
|
||||
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryRequest
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
|
|
@ -611,6 +611,17 @@ class DefaultWalletManagersFacade(
|
|||
return walletManager.fulfillRequirements(currencyType, signer)
|
||||
}
|
||||
|
||||
override suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean {
|
||||
val blockchain = Blockchain.fromId(network.id.value)
|
||||
val walletManager = getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
blockchain = blockchain,
|
||||
derivationPath = network.derivationPath.value,
|
||||
) ?: return false
|
||||
|
||||
return (walletManager as? UtxoBlockchainManager)?.allowConsolidation == true
|
||||
}
|
||||
|
||||
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
|
||||
if (tokens.isEmpty()) return
|
||||
|
||||
|
|
|
|||
|
|
@ -253,4 +253,12 @@ interface WalletManagersFacade {
|
|||
currency: CryptoCurrency,
|
||||
signer: CommonSigner,
|
||||
): SimpleResult
|
||||
|
||||
/**
|
||||
* Indicates UTXO consolidation availability
|
||||
*
|
||||
* @param userWalletId selected user wallet
|
||||
* @param network availability for network
|
||||
*/
|
||||
suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
|
||||
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.walletmanager.model.SmartContractMethod
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem as SdkTransactionHistoryItem
|
||||
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem as SdkTransactionHistoryItem
|
||||
|
||||
internal class SdkTransactionHistoryItemConverter(
|
||||
smartContractMethods: Map<String, SmartContractMethod>,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryState
|
||||
import com.tangem.blockchain.transactionhistory.TransactionHistoryState
|
||||
import com.tangem.domain.txhistory.models.TxHistoryState
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryState as SdkTransactionHistoryState
|
||||
import com.tangem.blockchain.transactionhistory.TransactionHistoryState as SdkTransactionHistoryState
|
||||
|
||||
internal class SdkTransactionHistoryStateConverter : Converter<SdkTransactionHistoryState, TxHistoryState> {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.domain.walletmanager.utils
|
||||
|
||||
import com.tangem.blockchain.common.txhistory.TransactionHistoryItem
|
||||
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.walletmanager.model.SmartContractMethod
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.domain.wallets.repository
|
||||
package com.tangem.domain.transaction
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.ParsedQrCode
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.domain.transaction.error
|
||||
|
||||
sealed class ValidateAddressError {
|
||||
data object AddressInWallet : ValidateAddressError()
|
||||
data object InvalidAddress : ValidateAddressError()
|
||||
data class DataError(val throwable: Throwable) : ValidateAddressError()
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Gets UTXO consolidation availability
|
||||
*/
|
||||
class IsUtxoConsolidationAvailableUseCase(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
suspend fun invokeSync(userWalletId: UserWalletId, network: Network) =
|
||||
walletManagersFacade.checkUtxoConsolidationAvailability(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.wallets.models.ParsedQrCode
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.transaction.error.ValidateAddressError
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Use case for validating wallet address.
|
||||
*/
|
||||
class ValidateWalletAddressUseCase(
|
||||
private val walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
address: String,
|
||||
currencyAddress: Set<NetworkAddress.Address>?,
|
||||
): Either<ValidateAddressError, Unit> {
|
||||
val isUtxoConsolidationAvailable =
|
||||
walletManagersFacade.checkUtxoConsolidationAvailability(userWalletId, network)
|
||||
val isCurrentAddress = currencyAddress?.any { it.value == address } ?: true
|
||||
|
||||
val isForbidSelfSend = isCurrentAddress && !isUtxoConsolidationAvailable
|
||||
val isValidAddress = walletAddressServiceRepository.validateAddress(userWalletId, network, address)
|
||||
|
||||
return when {
|
||||
!isValidAddress -> ValidateAddressError.InvalidAddress.left()
|
||||
isForbidSelfSend -> ValidateAddressError.AddressInWallet.left()
|
||||
else -> Unit.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
package com.tangem.domain.transaction.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
|
||||
/**
|
||||
* Use case for validating wallet memo.
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletAddressServiceRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Use case for validating wallet address.
|
||||
*/
|
||||
class ValidateWalletAddressUseCase(
|
||||
private val walletAddressServiceRepository: WalletAddressServiceRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
address: String,
|
||||
): Either<Throwable, Boolean> = withContext(dispatchers.io) {
|
||||
Either.catch {
|
||||
walletAddressServiceRepository.validateAddress(userWalletId, network, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,4 +5,9 @@ import androidx.fragment.app.Fragment
|
|||
interface DetailsEntryPoint {
|
||||
|
||||
fun entryFragment(): Fragment
|
||||
|
||||
companion object {
|
||||
|
||||
const val USER_WALLET_ID_KEY = "user_wallet_id"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,14 +15,18 @@ dependencies {
|
|||
|
||||
/* Project - API */
|
||||
implementation(projects.features.details.api)
|
||||
implementation(projects.features.tester.api)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.analytics.models)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ package com.tangem.features.details
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.di.RootAppComponentContext
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.message.EventMessageEffect
|
||||
import com.tangem.core.ui.message.EventMessageHandler
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
import com.tangem.features.details.component.preview.PreviewDetailsComponent
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
|
@ -16,10 +21,52 @@ internal class DetailsFragment : ComposeFragment() {
|
|||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
// @Inject
|
||||
// internal lateinit var componentFactory: DetailsComponent.Factory
|
||||
|
||||
@Inject
|
||||
internal lateinit var detailsRouter: DetailsRouter
|
||||
|
||||
@Inject
|
||||
@RootAppComponentContext
|
||||
internal lateinit var rootContext: AppComponentContext
|
||||
|
||||
private val component: DetailsComponent by lazy { initComponent() }
|
||||
|
||||
private val messageHandler = EventMessageHandler()
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
// TODO: Update to use the actual component in [REDACTED_TASK_KEY]
|
||||
PreviewDetailsComponent().View(modifier = modifier)
|
||||
component.View(modifier = modifier)
|
||||
|
||||
EventMessageEffect(
|
||||
messageHandler = messageHandler,
|
||||
snackbarHostState = component.snackbarHostState,
|
||||
)
|
||||
}
|
||||
|
||||
private fun initComponent(): DetailsComponent {
|
||||
// TODO: Uncomment in [REDACTED_JIRA]
|
||||
// val selectedUserWalletId = arguments?.getString(DetailsEntryPoint.USER_WALLET_ID_KEY)
|
||||
// ?.let(::UserWalletId)
|
||||
//
|
||||
//
|
||||
// requireNotNull(selectedUserWalletId) { "UserWalletId must be provided" }
|
||||
//
|
||||
// val context = rootContext.childByContext(
|
||||
// componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher),
|
||||
// messageHandler = messageHandler,
|
||||
// router = detailsRouter,
|
||||
// )
|
||||
//
|
||||
// return componentFactory.create(
|
||||
// context = context,
|
||||
// params = DetailsComponent.Params(
|
||||
// selectedUserWalletId = selectedUserWalletId,
|
||||
// ),
|
||||
// )
|
||||
|
||||
return PreviewDetailsComponent()
|
||||
}
|
||||
|
||||
companion object : DetailsEntryPoint {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.features.details
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.features.details.routing.DetailsRoute
|
||||
import com.tangem.features.tester.api.TesterRouter
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Remove after [REDACTED_JIRA]
|
||||
internal class DetailsRouter @Inject constructor(
|
||||
private val reduxNavController: ReduxNavController,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val testerRouter: TesterRouter,
|
||||
) : Router {
|
||||
|
||||
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
if (route is DetailsRoute) {
|
||||
when (route) {
|
||||
is DetailsRoute.Screen -> {
|
||||
reduxNavController.navigate(NavigationAction.NavigateTo(route.screen, bundle = route.params))
|
||||
}
|
||||
is DetailsRoute.Feedback -> {
|
||||
reduxStateHolder.sendFeedbackEmail()
|
||||
}
|
||||
is DetailsRoute.TesterMenu -> {
|
||||
testerRouter.startTesterScreen()
|
||||
}
|
||||
is DetailsRoute.Url -> {
|
||||
reduxNavController.navigate(NavigationAction.OpenUrl(route.url))
|
||||
}
|
||||
}
|
||||
onComplete(true)
|
||||
} else {
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
reduxNavController.popBackStack()
|
||||
onComplete(true)
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
if (route is DetailsRoute.Screen) {
|
||||
reduxNavController.popBackStack(route.screen)
|
||||
onComplete(true)
|
||||
} else {
|
||||
reduxNavController.getBackStack()
|
||||
onComplete(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.details.component
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
|
|
@ -7,6 +8,8 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
|
||||
interface DetailsComponent {
|
||||
|
||||
val snackbarHostState: SnackbarHostState
|
||||
|
||||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
fun View(modifier: Modifier)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.details.component.preview
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
|
|
@ -12,15 +13,18 @@ import kotlinx.coroutines.runBlocking
|
|||
|
||||
internal class PreviewDetailsComponent : DetailsComponent {
|
||||
|
||||
override val snackbarHostState: SnackbarHostState = SnackbarHostState()
|
||||
|
||||
private val previewBlocks = runBlocking {
|
||||
ItemsBuilder(
|
||||
walletConnectComponent = PreviewWalletConnectComponent(),
|
||||
userWalletListComponent = PreviewUserWalletListComponent(),
|
||||
router = PreviewRouter(),
|
||||
).buldAll()
|
||||
}
|
||||
|
||||
private val previewFooter = DetailsFooterUM(
|
||||
socials = SocialsBuilder(urlOpener = { /* no-op */ }).buildAll(),
|
||||
socials = SocialsBuilder(PreviewRouter()).buildAll(),
|
||||
appVersion = "1.0.0-preview",
|
||||
)
|
||||
|
||||
|
|
@ -33,6 +37,10 @@ internal class PreviewDetailsComponent : DetailsComponent {
|
|||
@Composable
|
||||
@Suppress("TopLevelComposableFunctions") // TODO: Remove this check
|
||||
override fun View(modifier: Modifier) {
|
||||
DetailsScreen(state = previewState, modifier = modifier)
|
||||
DetailsScreen(
|
||||
modifier = modifier,
|
||||
state = previewState,
|
||||
snackbarHostState = snackbarHostState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.details.component.preview
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
|
||||
internal class PreviewRouter : Router {
|
||||
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.details.routing
|
||||
|
||||
import android.os.Bundle
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
|
||||
// TODO: Remove after [REDACTED_JIRA]
|
||||
internal sealed class DetailsRoute : Route {
|
||||
|
||||
data class Screen(
|
||||
val screen: AppScreen,
|
||||
val params: Bundle? = null,
|
||||
) : DetailsRoute()
|
||||
|
||||
data class Url(val url: String) : DetailsRoute()
|
||||
|
||||
data object Feedback : DetailsRoute()
|
||||
|
||||
data object TesterMenu : DetailsRoute()
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.components.snackbar.TangemSnackbarHost
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.details.component.preview.PreviewDetailsComponent
|
||||
|
|
@ -31,7 +32,7 @@ private const val COLLAPSED_APP_BAR_THRESHOLD = 0.4f
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun DetailsScreen(state: DetailsUM, modifier: Modifier = Modifier) {
|
||||
internal fun DetailsScreen(state: DetailsUM, snackbarHostState: SnackbarHostState, modifier: Modifier = Modifier) {
|
||||
val backgroundColor = TangemTheme.colors.background.secondary
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
|
||||
|
|
@ -44,6 +45,12 @@ internal fun DetailsScreen(state: DetailsUM, modifier: Modifier = Modifier) {
|
|||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
containerColor = backgroundColor,
|
||||
snackbarHost = {
|
||||
TangemSnackbarHost(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
},
|
||||
topBar = { TopBar(state, scrollBehavior) },
|
||||
) { paddingValues ->
|
||||
Content(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
|
|
@ -7,6 +9,7 @@ import com.tangem.features.details.component.WalletConnectComponent
|
|||
import com.tangem.features.details.entity.DetailsItemUM
|
||||
import com.tangem.features.details.impl.BuildConfig
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.routing.DetailsRoute
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -14,6 +17,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
internal class ItemsBuilder(
|
||||
private val walletConnectComponent: WalletConnectComponent,
|
||||
private val userWalletListComponent: UserWalletListComponent,
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
suspend fun buldAll(): ImmutableList<DetailsItemUM> = buildList {
|
||||
|
|
@ -37,7 +41,7 @@ internal class ItemsBuilder(
|
|||
}
|
||||
}
|
||||
|
||||
fun buildUserWalletListBlock(): DetailsItemUM = DetailsItemUM.Component(
|
||||
private fun buildUserWalletListBlock(): DetailsItemUM = DetailsItemUM.Component(
|
||||
id = "user_wallet_list",
|
||||
content = {
|
||||
userWalletListComponent.View(modifier = it)
|
||||
|
|
@ -51,7 +55,7 @@ internal class ItemsBuilder(
|
|||
id = "buy_tangem_wallet",
|
||||
title = stringReference("Buy Tangem Wallet"), // TODO: Move to resources in [REDACTED_TASK_KEY]
|
||||
iconRes = R.drawable.ic_tangem_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.Url(BUY_TANGEM_URL)) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -63,7 +67,7 @@ internal class ItemsBuilder(
|
|||
id = "app_settings",
|
||||
title = resourceReference(R.string.app_settings_title),
|
||||
iconRes = R.drawable.ic_settings_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.Screen(AppScreen.AppSettings)) },
|
||||
).let(::add)
|
||||
|
||||
if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
|
|
@ -71,7 +75,7 @@ internal class ItemsBuilder(
|
|||
id = "tester_menu",
|
||||
title = stringReference(value = "Tester menu"),
|
||||
iconRes = R.drawable.ic_alert_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.TesterMenu) },
|
||||
).let(::add)
|
||||
}
|
||||
}.toImmutableList(),
|
||||
|
|
@ -84,14 +88,18 @@ internal class ItemsBuilder(
|
|||
id = "send_feedback",
|
||||
title = stringReference("Send feedback"), // TODO: Move to resources in [REDACTED_TASK_KEY]
|
||||
iconRes = R.drawable.ic_comment_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.Feedback) },
|
||||
),
|
||||
DetailsItemUM.Basic.Item(
|
||||
id = "disclaimer",
|
||||
title = resourceReference(R.string.disclaimer_title),
|
||||
iconRes = R.drawable.ic_text_24,
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
onClick = { router.push(DetailsRoute.Screen(AppScreen.Disclaimer)) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val BUY_TANGEM_URL = "https://buy.tangem.com/"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
package com.tangem.features.details.utils
|
||||
|
||||
import androidx.compose.ui.text.intl.Locale
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.features.details.entity.DetailsFooterUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.routing.DetailsRoute
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class SocialsBuilder(
|
||||
private val urlOpener: (url: String) -> Unit,
|
||||
private val router: Router,
|
||||
) {
|
||||
|
||||
fun buildAll(): ImmutableList<DetailsFooterUM.Social> = Social.all.map { social ->
|
||||
|
|
@ -21,11 +23,13 @@ internal class SocialsBuilder(
|
|||
private fun openUrl(social: Social) {
|
||||
val locale = Locale.current.region
|
||||
|
||||
if (locale == RUSSIA_LOCALE && social.urlRu != null) {
|
||||
urlOpener(social.urlRu)
|
||||
val url = if (locale == RUSSIA_LOCALE && social.urlRu != null) {
|
||||
social.urlRu
|
||||
} else {
|
||||
urlOpener(social.url)
|
||||
social.url
|
||||
}
|
||||
|
||||
router.push(DetailsRoute.Url(url))
|
||||
}
|
||||
|
||||
private enum class Social(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.features.send.impl.presentation.state.recipient
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.domain.transaction.error.ValidateAddressError
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
|
|
@ -18,10 +20,14 @@ internal class RecipientSendFactory(
|
|||
private val stateRouterProvider: Provider<StateRouter>,
|
||||
private val currentStateProvider: Provider<SendUiState>,
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val isUtxoConsolidationAvailableProvider: Provider<Boolean>,
|
||||
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
|
||||
) {
|
||||
private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientWalletListConverter()
|
||||
SendRecipientWalletListConverter(
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
isUtxoConsolidationAvailableProvider = isUtxoConsolidationAvailableProvider,
|
||||
)
|
||||
}
|
||||
private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientHistoryListConverter(
|
||||
|
|
@ -60,7 +66,10 @@ internal class RecipientSendFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getOnRecipientAddressValidState(value: String, isValidAddress: Boolean): SendUiState {
|
||||
fun getOnRecipientAddressValidState(
|
||||
value: String,
|
||||
maybeValidAddress: Either<ValidateAddressError, Unit>,
|
||||
): SendUiState {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
|
|
@ -73,21 +82,28 @@ internal class RecipientSendFactory(
|
|||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
|
||||
isPrimaryButtonEnabled = isValidMemo && maybeValidAddress.isRight(),
|
||||
isValidating = false,
|
||||
addressTextField = recipientState.addressTextField.copy(
|
||||
error = when {
|
||||
!isValidAddress -> resourceReference(R.string.send_recipient_address_error)
|
||||
isAddressInWallet -> resourceReference(R.string.send_error_address_same_as_wallet)
|
||||
else -> null
|
||||
},
|
||||
isError = value.isNotEmpty() && !isValidAddress || isAddressInWallet,
|
||||
error = maybeValidAddress.fold(
|
||||
ifLeft = {
|
||||
when (it) {
|
||||
ValidateAddressError.InvalidAddress -> resourceReference(
|
||||
R.string.send_recipient_address_error,
|
||||
)
|
||||
ValidateAddressError.AddressInWallet -> resourceReference(
|
||||
R.string.send_error_address_same_as_wallet,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
ifRight = { null },
|
||||
),
|
||||
isError = value.isNotEmpty() && maybeValidAddress.isLeft(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -131,13 +147,11 @@ internal class RecipientSendFactory(
|
|||
Timber.e("Failed to validateWalletMemoUseCase: $it")
|
||||
false
|
||||
}
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress && !isAddressInWallet,
|
||||
isPrimaryButtonEnabled = isValidMemo && isValidAddress,
|
||||
isValidating = false,
|
||||
memoTextField = recipientState.memoTextField?.copy(
|
||||
isError = value.isNotEmpty() && !isValidMemo,
|
||||
|
|
@ -162,11 +176,10 @@ internal class RecipientSendFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState {
|
||||
fun getHiddenRecentListState(isNotValid: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val isEditState = stateRouterProvider().isEditState
|
||||
val recipientState = state.getRecipientState(isEditState) ?: return state
|
||||
val isNotValid = isAddressInWallet || !isValidAddress
|
||||
return state.copyWrapped(
|
||||
isEditState = isEditState,
|
||||
recipientState = recipientState.copy(
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
package com.tangem.features.send.impl.presentation.state.recipient
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_DEFAULT_COUNT
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_KEY_TAG
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SendRecipientWalletListConverter :
|
||||
internal class SendRecipientWalletListConverter(
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
private val isUtxoConsolidationAvailableProvider: Provider<Boolean>,
|
||||
) :
|
||||
Converter<List<AvailableWallet?>, PersistentList<SendRecipientListContent>> {
|
||||
override fun convert(value: List<AvailableWallet?>): PersistentList<SendRecipientListContent> {
|
||||
return value.filterWallets().ifEmpty {
|
||||
|
|
@ -20,8 +26,18 @@ internal class SendRecipientWalletListConverter :
|
|||
|
||||
private fun List<AvailableWallet?>.filterWallets(): PersistentList<SendRecipientListContent> {
|
||||
var walletsCounter = 0
|
||||
val currentAddress: String = runCatching {
|
||||
cryptoCurrencyStatusProvider().value.networkAddress?.defaultAddress?.value
|
||||
}.getOrNull().orEmpty()
|
||||
|
||||
return this.filterNotNull()
|
||||
.filter { it.address.isNotBlank() }
|
||||
.filter {
|
||||
val isCoin = it.cryptoCurrency is CryptoCurrency.Coin
|
||||
val isNotSameAddress = it.address != currentAddress
|
||||
val isNotBlankAddress = it.address.isNotBlank()
|
||||
|
||||
isNotBlankAddress && isCoin && (isNotSameAddress || isUtxoConsolidationAvailableProvider())
|
||||
}
|
||||
.groupBy { item -> item.name }
|
||||
.values.map { wallets ->
|
||||
val groupedByWallet = wallets.groupBy { it.userWalletId }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.lifecycle.*
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.left
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
|
|
@ -34,10 +35,11 @@ import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
|||
import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.transaction.error.ValidateAddressError
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletAddressUseCase
|
||||
import com.tangem.domain.wallets.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.send.impl.navigation.InnerSendRouter
|
||||
import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource
|
||||
|
|
@ -93,6 +95,7 @@ internal class SendViewModel @Inject constructor(
|
|||
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
|
||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase,
|
||||
private val isUtxoConsolidationAvailableUseCase: IsUtxoConsolidationAvailableUseCase,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
|
|
@ -135,6 +138,7 @@ internal class SendViewModel @Inject constructor(
|
|||
stateRouterProvider = Provider { stateRouter },
|
||||
currentStateProvider = Provider { uiState },
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
isUtxoConsolidationAvailableProvider = Provider { isUtxoConsolidationAvailable },
|
||||
validateWalletMemoUseCase = validateWalletMemoUseCase,
|
||||
)
|
||||
|
||||
|
|
@ -198,6 +202,7 @@ internal class SendViewModel @Inject constructor(
|
|||
private var userWallet: UserWallet by Delegates.notNull()
|
||||
private var userWallets: List<AvailableWallet> = emptyList()
|
||||
private var isAmountSubtractAvailable: Boolean = false
|
||||
private var isUtxoConsolidationAvailable: Boolean = false
|
||||
private var isTapHelpPreviewEnabled: Boolean = false
|
||||
private var coinCryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
|
|
@ -248,6 +253,7 @@ internal class SendViewModel @Inject constructor(
|
|||
ifRight = { wallet ->
|
||||
userWallet = wallet
|
||||
checkIfSubtractAvailable()
|
||||
checkIfUtxoConsolidationAvailable()
|
||||
|
||||
val isSingleWalletWithToken = wallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
|
||||
val isMultiCurrency = wallet.isMultiCurrency
|
||||
|
|
@ -424,9 +430,6 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun List<UserWallet>.toAvailableWallets(): List<AvailableWallet> {
|
||||
val currentAddress: String = kotlin.runCatching {
|
||||
cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
|
||||
}.getOrNull().orEmpty()
|
||||
return filterNot { it.isLocked }
|
||||
.mapNotNull { wallet ->
|
||||
val addresses = if (!wallet.isMultiCurrency) {
|
||||
|
|
@ -440,16 +443,14 @@ internal class SendViewModel @Inject constructor(
|
|||
} else {
|
||||
getNetworkAddressesUseCase.invokeSync(wallet.walletId, cryptoCurrency.network)
|
||||
}
|
||||
addresses
|
||||
?.filter { it.address != currentAddress && it.cryptoCurrency is CryptoCurrency.Coin }
|
||||
?.map { (cryptoCurrency, address) ->
|
||||
AvailableWallet(
|
||||
name = wallet.name,
|
||||
address = address,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
userWalletId = wallet.walletId,
|
||||
)
|
||||
}
|
||||
addresses?.map { (cryptoCurrency, address) ->
|
||||
AvailableWallet(
|
||||
name = wallet.name,
|
||||
address = address,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
userWalletId = wallet.walletId,
|
||||
)
|
||||
}
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
|
|
@ -624,8 +625,15 @@ internal class SendViewModel @Inject constructor(
|
|||
uiState = recipientStateFactory.getOnRecipientAddressValidationStarted()
|
||||
val isValidAddress = validateAddress(value)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidState(value, isValidAddress)
|
||||
type?.let { analyticsEventHandler.send(SendAnalyticEvents.AddressEntered(it, isValidAddress)) }
|
||||
autoNextFromRecipient(type, isValidAddress)
|
||||
type?.let {
|
||||
analyticsEventHandler.send(
|
||||
SendAnalyticEvents.AddressEntered(
|
||||
it,
|
||||
isValidAddress.isRight(),
|
||||
),
|
||||
)
|
||||
}
|
||||
autoNextFromRecipient(type, isValidAddress.isRight())
|
||||
}
|
||||
}.saveIn(addressValidationJobHolder)
|
||||
}
|
||||
|
|
@ -636,23 +644,22 @@ internal class SendViewModel @Inject constructor(
|
|||
uiState = recipientStateFactory.getOnRecipientMemoValueChange(value, isValuePasted)
|
||||
uiState = recipientStateFactory.getOnRecipientAddressValidationStarted()
|
||||
val recipientState = uiState.getRecipientState(stateRouter.isEditState)
|
||||
val isValidAddress = validateAddress(recipientState?.addressTextField?.value.orEmpty())
|
||||
uiState = recipientStateFactory.getOnRecipientMemoValidState(value, isValidAddress)
|
||||
val maybeValidAddress = validateAddress(recipientState?.addressTextField?.value.orEmpty())
|
||||
uiState = recipientStateFactory.getOnRecipientMemoValidState(value, maybeValidAddress.isRight())
|
||||
}
|
||||
}.saveIn(memoValidationJobHolder)
|
||||
}
|
||||
|
||||
private suspend fun validateAddress(value: String): Boolean = runCatching {
|
||||
val isValidAddress = validateWalletAddressUseCase(
|
||||
private suspend fun validateAddress(value: String): Either<ValidateAddressError, Unit> = runCatching {
|
||||
val maybeValidAddress = validateWalletAddressUseCase(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
address = value,
|
||||
).getOrElse { false }
|
||||
val isAddressInWallet = cryptoCurrencyStatus.value.networkAddress?.availableAddresses
|
||||
?.any { it.value == value } ?: true
|
||||
onEnteredValidAddress(isValidAddress, isAddressInWallet)
|
||||
return isValidAddress
|
||||
}.getOrElse { false }
|
||||
currencyAddress = cryptoCurrencyStatus.value.networkAddress?.availableAddresses,
|
||||
)
|
||||
onEnteredValidAddress(maybeValidAddress.isLeft())
|
||||
maybeValidAddress
|
||||
}.getOrElse { ValidateAddressError.DataError(it).left() }
|
||||
|
||||
private suspend fun checkIfXrpAddressValue(value: String): Boolean {
|
||||
return BlockchainUtils.decodeRippleXAddress(value, cryptoCurrency.network.id.value)?.let { decodedAddress ->
|
||||
|
|
@ -665,11 +672,8 @@ internal class SendViewModel @Inject constructor(
|
|||
} ?: false
|
||||
}
|
||||
|
||||
private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) {
|
||||
uiState = recipientStateFactory.getHiddenRecentListState(
|
||||
isAddressInWallet = isAddressInWallet,
|
||||
isValidAddress = isValidAddress,
|
||||
)
|
||||
private fun onEnteredValidAddress(isNotValid: Boolean) {
|
||||
uiState = recipientStateFactory.getHiddenRecentListState(isNotValid = isNotValid)
|
||||
}
|
||||
|
||||
private fun autoNextFromRecipient(type: EnterAddressSource?, isValidAddress: Boolean) {
|
||||
|
|
@ -739,6 +743,13 @@ internal class SendViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun checkIfUtxoConsolidationAvailable() {
|
||||
isUtxoConsolidationAvailable = isUtxoConsolidationAvailableUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun callFeeUseCase(): Either<GetFeeError, TransactionFee>? {
|
||||
val isFromConfirmation = stateRouter.currentState.value.isFromConfirmation
|
||||
val amountState = uiState.getAmountState(isFromConfirmation) ?: return null
|
||||
|
|
|
|||
|
|
@ -87,4 +87,5 @@ dependencies {
|
|||
implementation(projects.features.send.api)
|
||||
implementation(projects.features.tester.api)
|
||||
implementation(projects.features.manageTokens.api)
|
||||
implementation(projects.features.details.api)
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScree
|
|||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
|
||||
import com.tangem.features.details.DetailsEntryPoint
|
||||
import com.tangem.features.managetokens.navigation.ExpandableState
|
||||
import com.tangem.features.managetokens.navigation.ManageTokensUi
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
||||
|
|
@ -116,8 +117,15 @@ internal class DefaultWalletRouter(
|
|||
navController.navigate(WalletRoute.OrganizeTokens.createRoute(userWalletId))
|
||||
}
|
||||
|
||||
override fun openDetailsScreen() {
|
||||
reduxNavController.navigate(action = NavigationAction.NavigateTo(AppScreen.Details))
|
||||
override fun openDetailsScreen(selectedWalletId: UserWalletId) {
|
||||
reduxNavController.navigate(
|
||||
action = NavigationAction.NavigateTo(
|
||||
screen = AppScreen.Details,
|
||||
bundle = bundleOf(
|
||||
DetailsEntryPoint.USER_WALLET_ID_KEY to selectedWalletId.stringValue,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun openOnboardingScreen() {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ internal interface InnerWalletRouter : WalletRouter {
|
|||
fun openOrganizeTokensScreen(userWalletId: UserWalletId)
|
||||
|
||||
/** Open details screen */
|
||||
fun openDetailsScreen()
|
||||
fun openDetailsScreen(selectedWalletId: UserWalletId)
|
||||
|
||||
/** Open onboarding screen */
|
||||
fun openOnboardingScreen()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
),
|
||||
)
|
||||
} else {
|
||||
router.openDetailsScreen()
|
||||
router.openDetailsScreen(stateHolder.getSelectedWalletId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ markdown = "0.7.2"
|
|||
# endregion Other libraries
|
||||
|
||||
# region Tangem
|
||||
tangemBlockchainSdk = "develop-652"
|
||||
tangemBlockchainSdk = "develop-654"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-359"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue