diff --git a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt index 7b68b75ab5..81c7160c71 100644 --- a/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt +++ b/app/src/main/java/com/tangem/tap/common/feedback/AdditionalFeedbackInfo.kt @@ -7,6 +7,7 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.userwallets.UserWalletIdBuilder import com.tangem.tap.common.extensions.stripZeroPlainString +import java.util.concurrent.CopyOnWriteArrayList class AdditionalFeedbackInfo { @@ -36,7 +37,7 @@ class AdditionalFeedbackInfo { var userWalletId: String = "" // wallets - val walletsInfo = mutableListOf() + val walletsInfo = CopyOnWriteArrayList() var onSendErrorWalletInfo: EmailWalletInfo? = null private set var signedHashesCount: String = "" @@ -74,9 +75,7 @@ class AdditionalFeedbackInfo { @Deprecated("Don't use it directly") fun setWalletsInfo(walletManagers: List) { walletsInfo.clear() - walletManagers.forEach { - walletsInfo.add(createEmailWalletInfo(it)) - } + walletsInfo.addAll(elements = walletManagers.map(::createEmailWalletInfo)) } fun updateOnSendError( diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt index b27f2e9aa5..4d30db8c36 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt @@ -188,8 +188,9 @@ private fun handleAction(action: Action, appState: () -> AppState?) { walletManagersFacade .getAll(userWallet.walletId) + .distinctUntilChanged() .onEach(infoHolder::setWalletsInfo) - .launchIn(scope) + .launchIn(mainScope) } } .flowOn(Dispatchers.IO) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt index c3cfe566f3..514b75eb10 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/UserTokensRepository.kt @@ -108,9 +108,9 @@ class UserTokensRepository( .fold( onSuccess = { response -> response.getOrThrow() + .also { storageService.saveUserTokens(userWalletId, it) } .tokens .mapNotNull(Currency.Companion::fromTokenResponse) - .also { storageService.saveUserTokens(userWalletId, it.toUserTokensResponse()) } .distinct() }, onFailure = { handleGetUserTokensFailure(userWalletId = userWalletId, error = it) }, 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 b28517d8a1..7c6a1285bf 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 @@ -74,7 +74,7 @@ internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modi val tokens = stateHolder.tokens.collectAsLazyPagingItems() TokensListContent( - isDifferentAddressesBlockVisible = stateHolder.isDifferentAddressesBlockVisible && !stateHolder.isLoading, + isDifferentAddressesBlockVisible = stateHolder.isDifferentAddressesBlockVisible, tokens = tokens, scaffoldPadding = scaffoldPadding, bottomMarginDp = floatingButtonHeight, @@ -125,8 +125,11 @@ private fun TokensListContent( state = state, contentPadding = PaddingValues(bottom = bottomMarginDp), ) { - if (isDifferentAddressesBlockVisible) { - item { DifferentAddressesWarning() } + item( + key = "DifferentAddressesWarning$isDifferentAddressesBlockVisible", + contentType = "DifferentAddressesWarning$isDifferentAddressesBlockVisible", + ) { + if (isDifferentAddressesBlockVisible) DifferentAddressesWarning() } tokens.itemKey(TokenItemState::composedId) diff --git a/app/src/main/res/layout/layout_onboarding_container_top.xml b/app/src/main/res/layout/layout_onboarding_container_top.xml index 9c4e9abe83..d3708a8bfc 100644 --- a/app/src/main/res/layout/layout_onboarding_container_top.xml +++ b/app/src/main/res/layout/layout_onboarding_container_top.xml @@ -111,8 +111,8 @@ Unit, modifier: Modifier = Modifier, + size: TangemButtonSize = TangemButtonSize.Default, showProgress: Boolean = false, enabled: Boolean = true, ) { @@ -88,6 +89,7 @@ fun PrimaryButton( colors = TangemButtonsDefaults.primaryButtonColors, enabled = enabled, showProgress = showProgress, + size = size, ) } @@ -100,6 +102,7 @@ fun PrimaryButtonIconEnd( @DrawableRes iconResId: Int, onClick: () -> Unit, modifier: Modifier = Modifier, + size: TangemButtonSize = TangemButtonSize.Default, showProgress: Boolean = false, enabled: Boolean = true, ) { @@ -111,6 +114,7 @@ fun PrimaryButtonIconEnd( colors = TangemButtonsDefaults.primaryButtonColors, enabled = enabled, showProgress = showProgress, + size = size, ) } @@ -144,6 +148,7 @@ fun SecondaryButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, + size: TangemButtonSize = TangemButtonSize.Default, showProgress: Boolean = false, enabled: Boolean = true, ) { @@ -155,6 +160,7 @@ fun SecondaryButton( colors = TangemButtonsDefaults.secondaryButtonColors, enabled = enabled, showProgress = showProgress, + size = size, ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt index e64aaf1965..134ad1ddac 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt @@ -14,6 +14,7 @@ enum class TangemButtonSize { Selector, Action, RoundedAction, + WideAction, } @Composable @@ -25,12 +26,15 @@ internal fun TangemButtonSize.toHeightDp(): Dp = when (this) { TangemButtonSize.Action, TangemButtonSize.RoundedAction, -> TangemTheme.dimens.size36 + TangemButtonSize.WideAction -> TangemTheme.dimens.size40 } @Composable @ReadOnlyComposable internal fun TangemButtonSize.toShape(): Shape = when (this) { - TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium + TangemButtonSize.Default, + TangemButtonSize.WideAction, + -> TangemTheme.shapes.roundedCornersMedium TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium @@ -40,7 +44,9 @@ internal fun TangemButtonSize.toShape(): Shape = when (this) { @Composable @ReadOnlyComposable internal fun TangemButtonSize.toIconPadding(): Dp = when (this) { - TangemButtonSize.Default -> TangemTheme.dimens.spacing4 + TangemButtonSize.Default, + TangemButtonSize.WideAction, + -> TangemTheme.dimens.spacing4 TangemButtonSize.Text -> TangemTheme.dimens.spacing8 TangemButtonSize.Selector -> 0.dp TangemButtonSize.Action, @@ -80,6 +86,12 @@ internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition): start = horizontalPadding.first, end = horizontalPadding.second, ) + TangemButtonSize.WideAction -> PaddingValues( + top = TangemTheme.dimens.spacing10, + bottom = TangemTheme.dimens.spacing10, + start = horizontalPadding.first, + end = horizontalPadding.second, + ) } } @@ -87,7 +99,9 @@ internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition): @ReadOnlyComposable internal fun TangemButtonSize.toHorizontalContentPadding(icon: TangemButtonIconPosition): Pair { return when (this) { - TangemButtonSize.Default -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32 + TangemButtonSize.Default, + TangemButtonSize.WideAction, + -> TangemTheme.dimens.spacing32 to TangemTheme.dimens.spacing32 TangemButtonSize.Text -> when (icon) { is TangemButtonIconPosition.None -> TangemTheme.dimens.spacing16 to TangemTheme.dimens.spacing16 is TangemButtonIconPosition.Start -> TangemTheme.dimens.spacing14 to TangemTheme.dimens.spacing16 diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt index 56b449ec3a..2f30c95eba 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt @@ -24,6 +24,7 @@ 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.* +import com.tangem.core.ui.components.buttons.common.TangemButtonSize import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme @@ -178,6 +179,7 @@ private fun SingleSecondaryButton(config: NotificationButtonsState.SecondaryButt text = config.text.resolveReference(), onClick = config.onClick, modifier = Modifier.fillMaxWidth(), + size = TangemButtonSize.WideAction, ) } @@ -189,12 +191,14 @@ private fun SinglePrimaryButton(config: NotificationButtonsState.PrimaryButtonCo iconResId = config.iconResId, onClick = config.onClick, modifier = Modifier.fillMaxWidth(), + size = TangemButtonSize.WideAction, ) } else { PrimaryButton( text = config.text.resolveReference(), onClick = config.onClick, modifier = Modifier.fillMaxWidth(), + size = TangemButtonSize.WideAction, ) } } @@ -206,12 +210,14 @@ private fun PairButtons(config: NotificationButtonsState.PairButtonsConfig) { text = config.secondaryText.resolveReference(), onClick = config.onSecondaryClick, modifier = Modifier.weight(weight = 1f), + size = TangemButtonSize.WideAction, ) PrimaryButton( text = config.primaryText.resolveReference(), onClick = config.onPrimaryClick, modifier = Modifier.weight(weight = 1f), + size = TangemButtonSize.WideAction, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt index 0187255098..f2572e21ff 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTypography.kt @@ -78,7 +78,7 @@ data class TangemTypography internal constructor( fontSize = 14.sp, fontWeight = FontWeight.Medium, letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp), - lineHeight = TextUnit(value = 16f, type = TextUnitType.Sp), + lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp), ), val caption1: TextStyle = TextStyle( fontFamily = RobotoFamily, diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 06fd253ea4..75ebd99c91 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -135,6 +135,7 @@ class DefaultWalletManagersFacade( userWalletId: UserWalletId, network: Network, addressType: AddressType, + contractAddress: String?, ): String { val blockchain = Blockchain.fromId(network.id.value) @@ -148,8 +149,12 @@ class DefaultWalletManagersFacade( "Unable to get a wallet manager for blockchain: $blockchain" } - val address = walletManager.wallet.addresses.find { it.type == addressType }?.value - return walletManager.wallet.getExploreUrl(address) + val address = walletManager + .wallet + .addresses + .find { it.type == addressType } + ?.value ?: walletManager.wallet.address + return blockchain.getExploreUrl(address, contractAddress) } override suspend fun getTxHistoryState(userWalletId: UserWalletId, network: Network): TxHistoryState { diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt index 8d10b2fa4d..2a17b00133 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -62,10 +62,16 @@ interface WalletManagersFacade { * @param userWalletId The ID of the user's wallet. * @param network The network. * @param addressType Address type. + * @param contractAddress Contract address if currency is Token. * * @return The network explorer URL, maybe empty if the wallet manager was not found. * */ - suspend fun getExploreUrl(userWalletId: UserWalletId, network: Network, addressType: AddressType): String + suspend fun getExploreUrl( + userWalletId: UserWalletId, + network: Network, + addressType: AddressType, + contractAddress: String?, + ): String /** * Returns transactions count diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt index 5b353a1918..5ba1af68eb 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/CryptoCurrencyStatus.kt @@ -75,13 +75,13 @@ data class CryptoCurrencyStatus( */ data class NoAccount( val amountToCreateAccount: BigDecimal, + override val fiatAmount: BigDecimal?, override val priceChange: BigDecimal?, override val fiatRate: BigDecimal?, override val networkAddress: NetworkAddress?, ) : Status(isError = false) { override val amount: BigDecimal = BigDecimal.ZERO - override val fiatAmount: BigDecimal = BigDecimal.ZERO } /** diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index dc6f9e3113..131c775492 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -31,6 +31,7 @@ internal class CurrencyStatusOperations( private fun createNoAccountStatus(status: NetworkStatus.NoAccount): CryptoCurrencyStatus.NoAccount = CryptoCurrencyStatus.NoAccount( amountToCreateAccount = status.amountToCreateAccount, + fiatAmount = if (quote == null) null else BigDecimal.ZERO, priceChange = quote?.priceChange, fiatRate = quote?.fiatRate, networkAddress = status.address, diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt index 5b6a22aa34..38338ba598 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/mock/MockTokensStates.kt @@ -60,6 +60,7 @@ internal object MockTokensStates { val tokenState7 = CryptoCurrencyStatus( currency = MockTokens.token7, value = CryptoCurrencyStatus.NoAccount( + fiatAmount = BigDecimal.ZERO, priceChange = MockQuotes.quote7.priceChange, fiatRate = MockQuotes.quote7.fiatRate, amountToCreateAccount = MockNetworks.amountToCreateAccount, @@ -70,6 +71,7 @@ internal object MockTokensStates { val tokenState8 = CryptoCurrencyStatus( currency = MockTokens.token8, value = CryptoCurrencyStatus.NoAccount( + fiatAmount = BigDecimal.ZERO, priceChange = MockQuotes.quote8.priceChange, fiatRate = MockQuotes.quote8.fiatRate, amountToCreateAccount = MockNetworks.amountToCreateAccount, @@ -80,6 +82,7 @@ internal object MockTokensStates { val tokenState9 = CryptoCurrencyStatus( currency = MockTokens.token9, value = CryptoCurrencyStatus.NoAccount( + fiatAmount = BigDecimal.ZERO, priceChange = MockQuotes.quote9.priceChange, fiatRate = MockQuotes.quote9.fiatRate, amountToCreateAccount = MockNetworks.amountToCreateAccount, @@ -90,6 +93,7 @@ internal object MockTokensStates { val tokenState10 = CryptoCurrencyStatus( currency = MockTokens.token10, value = CryptoCurrencyStatus.NoAccount( + fiatAmount = BigDecimal.ZERO, priceChange = MockQuotes.quote10.priceChange, fiatRate = MockQuotes.quote10.fiatRate, amountToCreateAccount = MockNetworks.amountToCreateAccount, diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetExploreUrlUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetExploreUrlUseCase.kt index cc29827d31..ef6b51fd7d 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetExploreUrlUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetExploreUrlUseCase.kt @@ -1,8 +1,8 @@ package com.tangem.domain.wallets.usecase import arrow.core.raise.catch -import com.tangem.domain.tokens.model.Network import com.tangem.blockchain.common.address.AddressType +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId @@ -12,11 +12,19 @@ class GetExploreUrlUseCase(private val walletsManagersFacade: WalletManagersFaca // FIXME: Handle error suspend operator fun invoke( userWalletId: UserWalletId, - network: Network, + currency: CryptoCurrency, addressType: AddressType = AddressType.Default, ): String { - return catch({ walletsManagersFacade.getExploreUrl(userWalletId, network, addressType) }) { - "" - } + return catch( + block = { + walletsManagersFacade.getExploreUrl( + userWalletId = userWalletId, + network = currency.network, + addressType = addressType, + contractAddress = (currency as? CryptoCurrency.Token)?.contractAddress, + ) + }, + catch = { "" }, + ) } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt index ce2b8786a6..26d332d53b 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractor.kt @@ -55,6 +55,7 @@ interface SwapInteractor { * @param fromToken [Currency] from which want to swap * @param toToken [Currency] that receive after swap * @param amountToSwap amount you want to swap + * @param selectedFee selected fee to swap * @return */ @Throws(IllegalStateException::class) @@ -63,6 +64,7 @@ interface SwapInteractor { fromToken: Currency, toToken: Currency, amountToSwap: String, + selectedFee: FeeType = FeeType.NORMAL, ): SwapState /** diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 08ccd867c4..7801531e8d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -167,6 +167,7 @@ internal class SwapInteractorImpl @Inject constructor( fromToken: Currency, toToken: Currency, amountToSwap: String, + selectedFee: FeeType, ): SwapState { syncWalletBalanceForTokens(networkId, listOf(fromToken, toToken)) val amountDecimal = toBigDecimalOrNull(amountToSwap) @@ -190,6 +191,7 @@ internal class SwapInteractorImpl @Inject constructor( fromToken = fromToken, toToken = toToken, amount = amount, + selectedFee = selectedFee, ) } else { loadQuoteData( @@ -454,6 +456,7 @@ internal class SwapInteractorImpl @Inject constructor( fromToken: Currency, toToken: Currency, amount: SwapAmount, + selectedFee: FeeType, ): SwapState { repository.prepareSwapTransaction( networkId = networkId, @@ -475,10 +478,14 @@ internal class SwapInteractorImpl @Inject constructor( derivationPath = derivationPath, ) val txFeeState = proxyFeesToFeeState(networkId, feeData) + val feeByPriority = when (selectedFee) { + FeeType.NORMAL -> txFeeState.normalFee.feeValue + FeeType.PRIORITY -> txFeeState.priorityFee.feeValue + } val isBalanceIncludeFeeEnough = - isBalanceEnough(networkId, fromToken, amount, txFeeState.priorityFee.feeValue) + isBalanceEnough(networkId, fromToken, amount, feeByPriority) val isFeeEnough = checkFeeIsEnough( - fee = txFeeState.normalFee.feeValue, + fee = feeByPriority, spendAmount = amount, networkId = networkId, fromToken = fromToken, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c62cff9622..75b0e2b05f 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -312,7 +312,7 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: } else { permissionState } - return when (val fee = uiState.fee) { + val updateState = when (val fee = uiState.fee) { is FeeState.Loaded -> { getUpdatedFeeStateForEnoughFee(uiState, fee, item, newSelectedItem, newPermissionState, isFeeEnough) } @@ -321,6 +321,15 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: } else -> uiState } + return if (isFeeEnough) { + updateState.copy( + warnings = uiState.warnings.filterNot { it is SwapWarning.InsufficientFunds }, + ) + } else { + updateState.copy( + warnings = uiState.warnings.plus(SwapWarning.InsufficientFunds), + ) + } } @Suppress("LongParameterList") @@ -348,6 +357,9 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: return uiState.copy( fee = newFeeState, permissionState = newPermissionState, + swapButton = uiState.swapButton.copy( + enabled = isFeeEnough, + ), ) } @@ -376,6 +388,9 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider: return uiState.copy( fee = newFeeState, permissionState = newPermissionState, + swapButton = uiState.swapButton.copy( + enabled = isFeeEnough, + ), ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index fbe4d1b64e..b1be139f19 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -205,6 +205,7 @@ internal class SwapViewModel @Inject constructor( fromToken = fromToken, toToken = toToken, amountToSwap = amount, + selectedFee = dataState.selectedFee?.feeType ?: FeeType.NORMAL, ) } }, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt index 0899710563..0710f995f6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsLoadedBalanceConverter.kt @@ -90,23 +90,33 @@ internal class TokenDetailsLoadedBalanceConverter( return when (status) { is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencySymbol) is CryptoCurrencyStatus.NoQuote -> MarketPriceBlockState.Error(currencySymbol) + is CryptoCurrencyStatus.NoAccount -> { + if (status.fiatRate == null) { + MarketPriceBlockState.Error(currencySymbol) + } else { + status.toContentConfig(currencySymbol) + } + } is CryptoCurrencyStatus.Loaded, is CryptoCurrencyStatus.Custom, is CryptoCurrencyStatus.MissedDerivation, - is CryptoCurrencyStatus.NoAccount, is CryptoCurrencyStatus.Unreachable, is CryptoCurrencyStatus.NoAmount, - -> MarketPriceBlockState.Content( - currencySymbol = currencySymbol, - price = formatPrice(status, appCurrencyProvider()), - priceChangeConfig = PriceChangeState.Content( - valueInPercent = formatPriceChange(status), - type = getPriceChangeType(status), - ), - ) + -> status.toContentConfig(currencySymbol) } } + private fun CryptoCurrencyStatus.Status.toContentConfig(currencySymbol: String): MarketPriceBlockState.Content { + return MarketPriceBlockState.Content( + currencySymbol = currencySymbol, + price = formatPrice(status = this, appCurrency = appCurrencyProvider()), + priceChangeConfig = PriceChangeState.Content( + valueInPercent = formatPriceChange(status = this), + type = getPriceChangeType(status = this), + ), + ) + } + private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeType { val priceChange = status.priceChange ?: return PriceChangeType.DOWN diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index bdfe161039..b28f7998b7 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.* import androidx.paging.cachedIn import arrow.core.getOrElse +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.address.AddressType import com.tangem.common.Provider import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -397,7 +398,7 @@ internal class TokenDetailsViewModel @Inject constructor( router.openUrl( url = getExploreUrlUseCase( userWalletId = userWalletId, - network = cryptoCurrency.network, + currency = cryptoCurrency, addressType = AddressType.Default, ), ) @@ -429,7 +430,7 @@ internal class TokenDetailsViewModel @Inject constructor( router.openUrl( url = getExploreUrlUseCase( userWalletId = userWalletId, - network = cryptoCurrency.network, + currency = cryptoCurrency, addressType = AddressType.valueOf(addressModel.type.name), ), ) @@ -438,12 +439,18 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onTransactionClick(txHash: String) { - router.openUrl( - url = getExplorerTransactionUrlUseCase( - txHash = txHash, - networkId = cryptoCurrency.network.id, - ), - ) + val blockchain = Blockchain.fromId(cryptoCurrency.network.id.value) + // TODO: Fix ton tx urls [REDACTED_TASK_KEY] + if (blockchain == Blockchain.TON || blockchain == Blockchain.TONTestnet) { + return + } else { + router.openUrl( + url = getExplorerTransactionUrlUseCase( + txHash = txHash, + networkId = cryptoCurrency.network.id, + ), + ) + } } override fun onRefreshSwipe() { 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 5a6d5f2a5c..30b447e54c 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 @@ -323,7 +323,7 @@ internal object WalletPreviewData { persistentListOf( WalletManageButton.Buy(enabled = true, onClick = {}), WalletManageButton.Send(enabled = true, onClick = {}), - WalletManageButton.Receive(onClick = {}), + WalletManageButton.Receive(enabled = true, onClick = {}), WalletManageButton.Sell(enabled = true, onClick = {}), WalletManageButton.Swap(enabled = true, onClick = {}), ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt index 18b589196a..fabf18401f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/components/WalletManageButton.kt @@ -15,6 +15,9 @@ import com.tangem.feature.wallet.impl.R @Immutable internal sealed class WalletManageButton(val config: ActionButtonConfig) { + /** Is click enabled */ + abstract val enabled: Boolean + /** Lambda be invoked when manage button is clicked */ abstract val onClick: () -> Unit @@ -24,7 +27,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * @property enabled button click availability * @property onClick lambda be invoked when Buy button is clicked */ - data class Buy(val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( + data class Buy(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_buy), iconResId = R.drawable.ic_plus_24, @@ -39,7 +42,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * @property enabled button click availability * @property onClick lambda be invoked when Send button is clicked */ - data class Send(val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( + data class Send(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_send), iconResId = R.drawable.ic_arrow_up_24, @@ -53,12 +56,12 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * * @property onClick lambda be invoked when Receive button is clicked */ - data class Receive(override val onClick: () -> Unit) : WalletManageButton( + data class Receive(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_receive), iconResId = R.drawable.ic_arrow_down_24, onClick = onClick, - enabled = true, + enabled = enabled, ), ) @@ -68,7 +71,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * @property enabled button click availability * @property onClick lambda be invoked when Sell button is clicked */ - data class Sell(val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( + data class Sell(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_sell), iconResId = R.drawable.ic_currency_24, @@ -83,7 +86,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) { * @property enabled button click availability * @property onClick lambda be invoked when Swap button is clicked */ - data class Swap(val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( + data class Swap(override val enabled: Boolean, override val onClick: () -> Unit) : WalletManageButton( config = ActionButtonConfig( text = TextReference.Res(id = R.string.common_swap), iconResId = R.drawable.ic_exchange_vertical_24, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt index b0a13a51de..01646a2c43 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletCryptoCurrencyActionsConverter.kt @@ -42,6 +42,7 @@ internal class WalletCryptoCurrencyActionsConverter( } is TokenActionsState.ActionState.Receive -> { WalletManageButton.Receive( + enabled = action.enabled, onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus) }, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLockedConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLockedConverter.kt index 8007a4cf4d..1f5766008f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLockedConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLockedConverter.kt @@ -72,7 +72,7 @@ internal class WalletLockedConverter( is WalletManageButton.Sell -> button.copy(enabled = false) is WalletManageButton.Send -> button.copy(enabled = false) is WalletManageButton.Swap -> button.copy(enabled = false) - is WalletManageButton.Receive -> button + is WalletManageButton.Receive -> button.copy(enabled = false) } } .toPersistentList() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt index 9d4b7bd566..fade7dadd7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt @@ -43,7 +43,7 @@ internal class WalletSingleCurrencyLoadedBalanceConverter( state.copy( walletsListConfig = getUpdatedSelectedWallet(status = status.value, state = state), - marketPriceBlockState = getMarketPriceState(status = status.value, currencyName = currencyName), + marketPriceBlockState = getMarketPriceState(status = status.value, currencySymbol = currencyName), ) } is WalletMultiCurrencyState.Content, @@ -54,32 +54,27 @@ internal class WalletSingleCurrencyLoadedBalanceConverter( } } - private fun getMarketPriceState(status: CryptoCurrencyStatus.Status, currencyName: String): MarketPriceBlockState { + private fun getMarketPriceState( + status: CryptoCurrencyStatus.Status, + currencySymbol: String, + ): MarketPriceBlockState { return when (status) { - is CryptoCurrencyStatus.NoQuote -> MarketPriceBlockState.Error(currencyName) is CryptoCurrencyStatus.Loaded, is CryptoCurrencyStatus.NoAmount, - -> MarketPriceBlockState.Content( - currencySymbol = currencyName, - price = formatPrice(status, appCurrencyProvider()), - priceChangeConfig = PriceChangeState.Content( - valueInPercent = formatPriceChange(status), - type = getPriceChangeType(status), - ), - ) - is CryptoCurrencyStatus.NoAccount -> MarketPriceBlockState.Content( - currencySymbol = currencyName, - price = formatPrice(status, appCurrencyProvider()), - priceChangeConfig = PriceChangeState.Content( - valueInPercent = formatPriceChange(status), - type = getPriceChangeType(status), - ), - ) - is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencyName) + -> status.toContentConfig(currencySymbol) + is CryptoCurrencyStatus.NoAccount -> { + if (status.fiatRate == null) { + MarketPriceBlockState.Error(currencySymbol) + } else { + status.toContentConfig(currencySymbol) + } + } + is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencySymbol) is CryptoCurrencyStatus.Custom, is CryptoCurrencyStatus.MissedDerivation, is CryptoCurrencyStatus.Unreachable, - -> MarketPriceBlockState.Error(currencyName) + is CryptoCurrencyStatus.NoQuote, + -> MarketPriceBlockState.Error(currencySymbol) } } @@ -138,6 +133,17 @@ internal class WalletSingleCurrencyLoadedBalanceConverter( ) } + private fun CryptoCurrencyStatus.Status.toContentConfig(currencySymbol: String): MarketPriceBlockState.Content { + return MarketPriceBlockState.Content( + currencySymbol = currencySymbol, + price = formatPrice(status = this, appCurrency = appCurrencyProvider()), + priceChangeConfig = PriceChangeState.Content( + valueInPercent = formatPriceChange(status = this), + type = getPriceChangeType(status = this), + ), + ) + } + private fun getPriceChangeType(status: CryptoCurrencyStatus.Status): PriceChangeType { val priceChange = status.priceChange ?: return PriceChangeType.DOWN diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt index 974d4c6aad..f34e374e45 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt @@ -148,7 +148,7 @@ internal class WalletSkeletonStateConverter( return persistentListOf( WalletManageButton.Buy(enabled = false, onClick = {}), WalletManageButton.Send(enabled = false, onClick = {}), - WalletManageButton.Receive(onClick = {}), + WalletManageButton.Receive(enabled = false, onClick = {}), WalletManageButton.Sell(enabled = false, onClick = {}), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index ea7437eeb8..a6a26d1828 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -1,6 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.ui import android.app.Activity +import android.content.Context +import android.content.ContextWrapper import android.widget.Toast import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material3.SnackbarHostState @@ -57,7 +59,7 @@ internal fun WalletEventEffect( .addOnCompleteListener { handleOnCompleteRequestTask( reviewManager = reviewManager, - activity = context as? Activity ?: return@addOnCompleteListener, + activity = context.findActivity(), task = it, onDismissClick = value.onDismissClick, ) @@ -69,6 +71,15 @@ internal fun WalletEventEffect( ) } +private fun Context.findActivity(): Activity { + var context = this + while (context is ContextWrapper) { + if (context is Activity) return context + context = context.baseContext + } + error("Permissions should be called in the context of an Activity") +} + private fun handleOnCompleteRequestTask( reviewManager: ReviewManager, activity: Activity, 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 e294047072..c16ef38409 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 @@ -646,10 +646,23 @@ internal class WalletViewModel @Inject constructor( event = TokenScreenAnalyticsEvent.ButtonSend(cryptoCurrencyStatus.currency.symbol), ) - val currency = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return - viewModelScope.launch(dispatchers.io) { - val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) + val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex) + when (cryptoCurrencyStatus.currency) { + is CryptoCurrency.Coin -> { + uiState = stateFactory.getStateWithClosedBottomSheet() + reduxStateHolder.dispatch( + action = TradeCryptoAction.New.SendCoin( + userWallet = userWallet, + coinStatus = cryptoCurrencyStatus, + ), + ) + } + is CryptoCurrency.Token -> sendToken(userWallet, cryptoCurrencyStatus) + } + } + private fun sendToken(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus) { + viewModelScope.launch(dispatchers.io) { getNetworkCoinStatusUseCase( userWalletId = userWallet.walletId, networkId = cryptoCurrencyStatus.currency.network.id, @@ -659,10 +672,11 @@ internal class WalletViewModel @Inject constructor( .take(count = 1) .collectLatest { it.onRight { coinStatus -> + uiState = stateFactory.getStateWithClosedBottomSheet() reduxStateHolder.dispatch( action = TradeCryptoAction.New.SendToken( userWallet = userWallet, - tokenCurrency = currency, + tokenCurrency = requireNotNull(cryptoCurrencyStatus.currency as? CryptoCurrency.Token), tokenFiatRate = cryptoCurrencyStatus.value.fiatRate, coinFiatRate = coinStatus.value.fiatRate, ), @@ -805,18 +819,18 @@ internal class WalletViewModel @Inject constructor( private fun openExplorer() { val state = uiState as? WalletState.ContentState ?: return - val currencyNetwork = singleWalletCryptoCurrencyStatus?.currency?.network ?: return + val currency = singleWalletCryptoCurrencyStatus?.currency ?: return viewModelScope.launch(dispatchers.main) { val userWalletId = getWallet(state.walletsListConfig.selectedWalletIndex).walletId - val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currencyNetwork) + val addresses = walletManagersFacade.getAddress(userWalletId = userWalletId, network = currency.network) if (addresses.size == 1) { router.openUrl( url = getExploreUrlUseCase( userWalletId = userWalletId, - network = currencyNetwork, + currency = currency, addressType = AddressType.Default, ), ) @@ -834,7 +848,7 @@ internal class WalletViewModel @Inject constructor( onClick = { onAddressTypeSelected( userWalletId = userWalletId, - currencyNetwork = currencyNetwork, + currency = currency, addressModel = it, ) }, @@ -846,14 +860,14 @@ internal class WalletViewModel @Inject constructor( private fun onAddressTypeSelected( userWalletId: UserWalletId, - currencyNetwork: Network, + currency: CryptoCurrency, addressModel: AddressModel, ) { viewModelScope.launch(dispatchers.main) { router.openUrl( url = getExploreUrlUseCase( userWalletId = userWalletId, - network = currencyNetwork, + currency = currency, addressType = AddressType.valueOf(addressModel.type.name), ), ) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 758d9fa77f..4b09efb3ab 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -81,7 +81,7 @@ spr-client = "3.6.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.0-365" +tangemBlockchainSdk = "release-app_5.0-368" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.0-308" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds