diff --git a/app/src/main/java/com/tangem/tap/di/Qualifiers.kt b/app/src/main/java/com/tangem/tap/di/Qualifiers.kt index df7fab7779..94ba51a71e 100644 --- a/app/src/main/java/com/tangem/tap/di/Qualifiers.kt +++ b/app/src/main/java/com/tangem/tap/di/Qualifiers.kt @@ -1,8 +1,10 @@ @file:Suppress("Filename") + package com.tangem.tap.di import javax.inject.Qualifier +@Deprecated("Use one in Core Utils") @Qualifier @Retention(AnnotationRetention.BINARY) annotation class DelayedWork \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt index 2713162e91..9db1abac3b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/AmountTextField.kt @@ -160,10 +160,10 @@ private fun String.preserveDecimalSymbol(newValue: String, decimalSymbol: Char) private fun String.preserveTrailingZeros(newValue: String, parsedDecimal: BigDecimal?, decimalSymbol: Char): String { val trailingZeros = newValue.split(decimalSymbol).getOrNull(1)?.takeLastWhile { it == '0' }.orEmpty() - return if (parsedDecimal?.scale() == 0 && trailingZeros.isNotEmpty()) { - "$this$decimalSymbol$trailingZeros" - } else { - this.plus(trailingZeros) + return when { + this.endsWith('0') -> this + parsedDecimal?.scale() == 0 && trailingZeros.isNotEmpty() -> "$this$decimalSymbol$trailingZeros" + else -> this.plus(trailingZeros) } } diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/DelayedWork.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/DelayedWork.kt new file mode 100644 index 0000000000..8c822c02ed --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/DelayedWork.kt @@ -0,0 +1,7 @@ +package com.tangem.utils.coroutines + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class DelayedWork \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/di/DelayedWorkCoroutineModule.kt b/core/utils/src/main/java/com/tangem/utils/di/DelayedWorkCoroutineModule.kt new file mode 100644 index 0000000000..4f5ad6e74f --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/di/DelayedWorkCoroutineModule.kt @@ -0,0 +1,23 @@ +package com.tangem.utils.di + +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.DelayedWork +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DelayedWorkCoroutineModule { + + @Provides + @Singleton + @DelayedWork + fun provideDelayedWorkCoroutineScope(coroutineDispatcherProvider: CoroutineDispatcherProvider): CoroutineScope { + return CoroutineScope(SupervisorJob() + coroutineDispatcherProvider.io) + } +} \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 57f55c1aab..fe6ab4046f 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -13,6 +13,7 @@ import com.tangem.domain.common.extensions.fromNetworkId import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.DemoConfig import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.tokens.repository.NetworksRepository @@ -78,7 +79,10 @@ internal class DefaultNetworksRepository( return blockchain == Blockchain.Aptos } - override suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List { + override suspend fun getNetworkAddresses( + userWalletId: UserWalletId, + network: Network, + ): List { // Get list of currencies matching [network] val currencies = getCurrencies(userWalletId) .filter { currency -> network.id == currency.network.id } @@ -87,9 +91,12 @@ internal class DefaultNetworksRepository( if (currencies.toList().isEmpty()) return emptyList() return currencies.toList().map { currency -> - walletManagersFacade.getAddresses(userWalletId, currency.network) - .firstOrNull { it.type == AddressType.Default } - ?.value.orEmpty() + CryptoCurrencyAddress( + cryptoCurrency = currency, + address = walletManagersFacade.getAddresses(userWalletId, currency.network) + .firstOrNull { it.type == AddressType.Default } + ?.value.orEmpty(), + ) } } diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyAddress.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyAddress.kt new file mode 100644 index 0000000000..73b8902323 --- /dev/null +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyAddress.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.tokens.model + +data class CryptoCurrencyAddress( + val cryptoCurrency: CryptoCurrency, + val address: String, +) \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt index 0fa37a5a07..654c263b3e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkAddressesUseCase.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens +import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.wallets.models.UserWalletId @@ -8,7 +9,7 @@ class GetNetworkAddressesUseCase( internal val networksRepository: NetworksRepository, ) { - suspend fun invokeSync(userWalletId: UserWalletId, network: Network): List { + suspend fun invokeSync(userWalletId: UserWalletId, network: Network): List { return networksRepository.getNetworkAddresses(userWalletId, network) } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt index ebece1356e..271cdb4fcf 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/NetworksRepository.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens.repository +import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId @@ -46,5 +47,5 @@ interface NetworksRepository { fun isNeedToCreateAccountWithoutReserve(network: Network): Boolean - suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List + suspend fun getNetworkAddresses(userWalletId: UserWalletId, network: Network): List } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt index ecf1d2a23b..34abccfa04 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockNetworksRepository.kt @@ -3,6 +3,7 @@ package com.tangem.domain.tokens.repository import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.core.error.DataError +import com.tangem.domain.tokens.model.CryptoCurrencyAddress import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.model.NetworkStatus import com.tangem.domain.wallets.models.UserWalletId @@ -34,4 +35,10 @@ internal class MockNetworksRepository( } override fun isNeedToCreateAccountWithoutReserve(network: Network) = false + override suspend fun getNetworkAddresses( + userWalletId: UserWalletId, + network: Network, + ): List { + return emptyList() + } } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt index 8a7d5e7f3a..b6959e4a35 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/domain/AvailableWallet.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.impl.presentation.domain import androidx.compose.runtime.Immutable +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.wallets.models.UserWalletId /** @@ -15,4 +16,5 @@ data class AvailableWallet( val name: String, val userWalletId: UserWalletId, val address: String, + val cryptoCurrency: CryptoCurrency, ) \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt index f3008dc000..65ab1d68bf 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/previewdata/RecipientStatePreviewData.kt @@ -2,12 +2,25 @@ package com.tangem.features.send.impl.presentation.state.previewdata import androidx.compose.foundation.text.KeyboardOptions import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fields.SendTextField import kotlinx.collections.immutable.persistentListOf internal object RecipientStatePreviewData { + private val defaultRecentItem = SendRecipientListContent( + id = "sanctus", + title = stringReference("address"), + subtitle = stringReference("0.001 BTC"), + timestamp = stringReference("1.01.1970, 00:00"), + subtitleEndOffset = 0, + subtitleIconRes = R.drawable.ic_arrow_down_24, + isVisible = true, + isLoading = false, + ) + val recipientState = SendStates.RecipientState( addressTextField = SendTextField.RecipientAddress( value = "0x23948239805671983476598176", @@ -19,8 +32,14 @@ internal object RecipientStatePreviewData { error = null, ), memoTextField = null, - recent = persistentListOf(), - wallets = persistentListOf(), + recent = persistentListOf( + defaultRecentItem.copy(id = "1"), + defaultRecentItem.copy(id = "2"), + defaultRecentItem.copy(id = "3"), + ), + wallets = persistentListOf( + defaultRecentItem.copy(id = "4", subtitle = stringReference("Wallet")), + ), network = "Ethereum", isValidating = false, isPrimaryButtonEnabled = true, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt index 9ef9f60a21..153a2686a1 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/ListItemWithIcon.kt @@ -95,11 +95,13 @@ private fun ListItemWithIcon( address = title, modifier = Modifier .padding(vertical = TangemTheme.dimens.spacing8) - .size(TangemTheme.dimens.size36) - .clip(RoundedCornerShape(TangemTheme.dimens.radius18)), + .size(TangemTheme.dimens.size40) + .clip(RoundedCornerShape(TangemTheme.dimens.radius20)), ) Column( - modifier = Modifier.padding(start = TangemTheme.dimens.spacing12), + modifier = Modifier + .height(TangemTheme.dimens.size36) + .padding(start = TangemTheme.dimens.spacing12), verticalArrangement = Arrangement.SpaceBetween, ) { EllipsisText( @@ -118,7 +120,8 @@ private fun ListItemWithIcon( tint = TangemTheme.colors.icon.informative, modifier = Modifier .size(TangemTheme.dimens.size16) - .background(TangemTheme.colors.background.tertiary, CircleShape), + .background(TangemTheme.colors.background.tertiary, CircleShape) + .padding(TangemTheme.dimens.spacing2), ) } val (text, offset) = remember(subtitle, info) { @@ -152,11 +155,11 @@ private fun ListItemLoading(modifier: Modifier = Modifier) { CircleShimmer( modifier = Modifier .padding(vertical = TangemTheme.dimens.spacing8) - .size(TangemTheme.dimens.size36), + .size(TangemTheme.dimens.size40), ) Column( modifier = Modifier - .height(TangemTheme.dimens.size32) + .height(TangemTheme.dimens.size36) .padding(start = TangemTheme.dimens.spacing12), verticalArrangement = Arrangement.SpaceBetween, ) { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index e37a0ced3d..66fca69ea6 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -17,6 +17,9 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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.PreviewParameterProvider import com.tangem.common.Strings.STARS import com.tangem.core.ui.components.inputrow.InputRowRecipient import com.tangem.core.ui.extensions.resolveReference @@ -26,6 +29,8 @@ import com.tangem.features.send.impl.presentation.analytics.EnterAddressSource import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData +import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub import com.tangem.features.send.impl.presentation.ui.common.FooterContainer import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import kotlinx.collections.immutable.ImmutableList @@ -150,7 +155,7 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo TangemTheme.dimens.spacing0 to TangemTheme.dimens.spacing8 } val topRadius = if (isFirst) { - TangemTheme.dimens.radius12 + TangemTheme.dimens.radius16 } else { TangemTheme.dimens.radius0 } @@ -170,7 +175,7 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo .background(TangemTheme.colors.background.action) .padding( top = paddingFromTop, - bottom = TangemTheme.dimens.spacing8, + bottom = TangemTheme.dimens.spacing12, start = TangemTheme.dimens.spacing12, end = TangemTheme.dimens.spacing12, ), @@ -242,4 +247,25 @@ private fun AnimateRecentAppearance(isVisible: Boolean, content: @Composable () Box(modifier = Modifier.fillMaxWidth()) } } +} + +@Preview(widthDp = 360, heightDp = 800) +@Composable +private fun SendRecipientContent_Preview( + @PreviewParameter(SendRecipientContentPreviewProvider::class) recipientState: SendStates.RecipientState, +) { + TangemTheme(isDark = false) { + SendRecipientContent( + uiState = recipientState, + clickIntents = SendClickIntentsStub, + isBalanceHidden = false, + ) + } +} + +private class SendRecipientContentPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + RecipientStatePreviewData.recipientState, + ) } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 65d93773c7..c14b26757a 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -56,9 +56,11 @@ import com.tangem.features.send.impl.presentation.state.fee.* import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.DelayedWork import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -97,6 +99,7 @@ internal class SendViewModel @Inject constructor( private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase, private val fetchPendingTransactionsUseCase: FetchPendingTransactionsUseCase, + @DelayedWork private val coroutineScope: CoroutineScope, currencyChecksRepository: CurrencyChecksRepository, isFeeApproximateUseCase: IsFeeApproximateUseCase, validateWalletMemoUseCase: ValidateWalletMemoUseCase, @@ -415,8 +418,11 @@ internal class SendViewModel @Inject constructor( } } - private suspend fun List.toAvailableWallets(): List = - filterNot { it.walletId == userWalletId || it.isLocked } + private suspend fun List.toAvailableWallets(): List { + val currentAddress: String = kotlin.runCatching { + cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value + }.getOrNull().orEmpty() + return filterNot { it.isLocked } .mapNotNull { wallet -> val addresses = if (!wallet.isMultiCurrency) { getCryptoCurrencyUseCase(wallet.walletId).getOrNull()?.let { @@ -429,14 +435,18 @@ internal class SendViewModel @Inject constructor( } else { getNetworkAddressesUseCase.invokeSync(wallet.walletId, cryptoCurrency.network) } - addresses?.map { address -> - AvailableWallet( - name = wallet.name, - address = address, - userWalletId = wallet.walletId, - ) - }?.fastDistinctBy { it.address } + addresses + ?.filter { it.address != currentAddress } + ?.map { (cryptoCurrency, address) -> + AvailableWallet( + name = wallet.name, + address = address, + cryptoCurrency = cryptoCurrency, + userWalletId = wallet.walletId, + ) + }?.fastDistinctBy { it.address } }.flatten() + } private suspend fun getTxHistory() { val txHistoryList = getFixedTxHistoryItemsUseCase.getSync( @@ -890,11 +900,9 @@ internal class SendViewModel @Inject constructor( } private fun scheduleUpdates() { - viewModelScope.launch(dispatchers.main) { + coroutineScope.launch { // we should update network to find pending tx after 1 sec fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrency.network)) - } - viewModelScope.launch(dispatchers.main) { // we should update network for new balance updateDelayedCurrencyStatusUseCase( userWalletId = userWallet.walletId, @@ -954,7 +962,7 @@ internal class SendViewModel @Inject constructor( private companion object { const val CHECK_FEE_UPDATE_DELAY = 60_000L - const val BALANCE_UPDATE_DELAY = 10_000L + const val BALANCE_UPDATE_DELAY = 11_000L const val RU_LOCALE = "ru" const val EN_LOCALE = "en"