From f7b10f3ed59c919f726dedd220f7ef5846429f4d Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 13 Nov 2025 19:58:22 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .gitignore | 1 + .../common/ui/notifications/NotificationUM.kt | 18 +-- core/res/src/main/res/values-ja/strings.xml | 2 +- .../tangem/core/ui/components/label/Label.kt | 28 +++-- .../ui/components/label/entity/LabelUM.kt | 1 + ...TransactionDataToTxHistoryItemConverter.kt | 57 +++++++--- data/yield-supply/build.gradle.kts | 1 + .../supply/DefaultYieldSupplyRepository.kt | 54 ++++++++- .../analytics/StakingAnalyticsEvent.kt | 12 +- .../yield/supply/YieldSupplyRepository.kt | 19 +++- .../presentation/model/StakingClickIntents.kt | 3 + .../impl/presentation/model/StakingModel.kt | 18 +++ .../state/stub/StakingClickIntentsStub.kt | 2 + .../AddStakingNotificationsTransformer.kt | 1 + .../confirm/SendWithSwapConfirmComponent.kt | 2 +- .../confirm/model/SendWithSwapConfirmModel.kt | 3 +- .../sendviaswap/model/SendWithSwapModel.kt | 6 +- .../domain/GetMultiWalletWarningsFactory.kt | 36 +++--- .../impl/apy/ui/YieldSupplyApyContent.kt | 7 +- .../supply/impl/main/entity/YieldSupplyUM.kt | 1 + .../impl/main/model/YieldSupplyModel.kt | 90 +++++++++++---- .../impl/main/ui/YieldSupplyBlockContent.kt | 23 +++- .../impl/promo/ui/YieldSupplyPromoContent.kt | 15 ++- .../active/model/YieldSupplyActiveModel.kt | 2 +- .../active/ui/YieldSupplyActiveContent.kt | 20 +++- .../ui/YieldSupplyFeePolicyContent.kt | 7 +- .../model/YieldSupplyStartEarningModel.kt | 2 +- gradle/tangem_dependencies.toml | 2 +- .../extension/AppExtensionConfigurations.kt | 11 +- .../plugin/configuration/model/AppConfig.kt | 2 - .../utils/VersionNameProvider.kt | 105 ++++++++++++++++++ 31 files changed, 446 insertions(+), 105 deletions(-) create mode 100644 plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/utils/VersionNameProvider.kt diff --git a/.gitignore b/.gitignore index cb56a9e269..345207b4eb 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ app/src/external/google-services.json # Kotlin Plugin .kotlin/ +find-latest-release-branch.output diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index 662a404f90..7425a7aa0f 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -275,14 +275,6 @@ sealed class NotificationUM(val config: NotificationConfig) { title = resourceReference(id = R.string.yield_module_balance_info_sheet_title, wrappedList(tokenName)), subtitle = resourceReference(id = R.string.yield_module_balance_info_sheet_subtitle), ) - - data class YieldSupplyNotAllAmountSupplied(val formattedAmount: String, val symbol: String) : Warning( - title = resourceReference( - id = R.string.yield_module_amount_not_transfered_to_aave_title, - formatArgs = wrappedList(formattedAmount, symbol), - ), - subtitle = TextReference.EMPTY, - ) } open class Info( @@ -301,7 +293,15 @@ sealed class NotificationUM(val config: NotificationConfig) { onCloseClick = onCloseClick, iconTint = iconTint, ), - ) + ) { + data class YieldSupplyNotAllAmountSupplied(val formattedAmount: String, val symbol: String) : Info( + title = resourceReference( + id = R.string.yield_module_amount_not_transfered_to_aave_title, + formatArgs = wrappedList(formattedAmount, symbol), + ), + subtitle = TextReference.EMPTY, + ) + } sealed interface Cardano { diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 211f153257..797ee47246 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -269,7 +269,7 @@ 無料 送信元 アドレスを同期する - はじめる + もっと詳しく トークンを取得 プロバイダーへ移動 トークンへ移動 diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/label/Label.kt b/core/ui/src/main/java/com/tangem/core/ui/components/label/Label.kt index 951e6a5042..2cc6209978 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/label/Label.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/label/Label.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview @@ -68,9 +69,18 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) { horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = modifier .padding(horizontal = 4.dp) - .background( - color = backgroundColor, - shape = TangemTheme.shapes.roundedCorners8, + .clip(TangemTheme.shapes.roundedCorners8) + .background(color = backgroundColor) + .then( + if (state.onClick != null) { + Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(), + onClick = state.onClick, + ) + } else { + Modifier + }, ) .padding(horizontal = 8.dp, vertical = 4.dp), ) { @@ -86,11 +96,13 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) { imageVector = ImageVector.vectorResource(wrappedIcon), tint = iconColor, contentDescription = null, - modifier = Modifier.size(16.dp).clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(bounded = false), - onClick = { state.onIconClick?.invoke() }, - ), + modifier = Modifier + .size(16.dp) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + onClick = { state.onIconClick?.invoke() }, + ), ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/label/entity/LabelUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/label/entity/LabelUM.kt index 1be654c994..9982a897b0 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/label/entity/LabelUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/label/entity/LabelUM.kt @@ -8,6 +8,7 @@ data class LabelUM( val style: LabelStyle, @DrawableRes val icon: Int? = null, val onIconClick: (() -> Unit)? = null, + val onClick: (() -> Unit)? = null, ) enum class LabelStyle { diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt index 5701b7eb2e..dae65d3ec7 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/utils/TransactionDataToTxHistoryItemConverter.kt @@ -1,10 +1,13 @@ package com.tangem.data.walletmanager.utils import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras +import com.tangem.blockchain.blockchains.ethereum.tokenmethods.ApprovalERC20TokenCallData import com.tangem.blockchain.common.* import com.tangem.blockchain.yieldsupply.providers.ethereum.factory.EthereumYieldSupplyDeployCallData import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyInitTokenCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyReactivateTokenCallData import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address import com.tangem.domain.models.network.TxInfo import com.tangem.utils.converter.Converter @@ -37,25 +40,12 @@ internal class TransactionDataToTxHistoryItemConverter( addressType = TxInfo.AddressType.User(value.destinationAddress), ), sourceType = TxInfo.SourceType.Single(value.sourceAddress), - interactionAddressType = TxInfo.InteractionAddressType.User( - address = if (isOutgoing) value.destinationAddress else value.sourceAddress, - ), + interactionAddressType = getInteractionAddressType(value, isOutgoing), status = when (value.status) { TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed }, - type = when (val extras = value.extras) { - is EthereumTransactionExtras -> { - when (extras.callData) { - is EthereumYieldSupplyDeployCallData, - is EthereumYieldSupplyEnterCallData, - -> TxInfo.TransactionType.YieldSupply.Enter - is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit - else -> TxInfo.TransactionType.Transfer - } - } - else -> TxInfo.TransactionType.Transfer - }, + type = getTransactionType(value.extras), amount = amount, ) } @@ -89,4 +79,41 @@ internal class TransactionDataToTxHistoryItemConverter( return amountToken.contractAddress.equals(feeToken.contractAddress, ignoreCase = true) && amountToken.symbol.equals(feeToken.symbol, ignoreCase = true) } + + private fun getInteractionAddressType( + value: TransactionData.Uncompiled, + isOutgoing: Boolean, + ): TxInfo.InteractionAddressType = when (val extras = value.extras) { + is EthereumTransactionExtras -> { + when (val callData = extras.callData) { + is ApprovalERC20TokenCallData -> TxInfo.InteractionAddressType.Contract( + address = callData.spenderAddress, + ) + else -> TxInfo.InteractionAddressType.User( + address = if (isOutgoing) value.destinationAddress else value.sourceAddress, + ) + } + } + else -> TxInfo.InteractionAddressType.User( + address = if (isOutgoing) value.destinationAddress else value.sourceAddress, + ) + } + + private fun getTransactionType(extras: TransactionExtras?): TxInfo.TransactionType { + return when (extras) { + is EthereumTransactionExtras -> { + when (extras.callData) { + is EthereumYieldSupplyDeployCallData, + is EthereumYieldSupplyReactivateTokenCallData, + is EthereumYieldSupplyInitTokenCallData, + is EthereumYieldSupplyEnterCallData, + -> TxInfo.TransactionType.YieldSupply.Enter + is EthereumYieldSupplyExitCallData -> TxInfo.TransactionType.YieldSupply.Exit + is ApprovalERC20TokenCallData -> TxInfo.TransactionType.Approve + else -> TxInfo.TransactionType.Transfer + } + } + else -> TxInfo.TransactionType.Transfer + } + } } \ No newline at end of file diff --git a/data/yield-supply/build.gradle.kts b/data/yield-supply/build.gradle.kts index 73b4ac8ab2..b53babcf05 100644 --- a/data/yield-supply/build.gradle.kts +++ b/data/yield-supply/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(projects.domain.yieldSupply.models) implementation(projects.domain.walletManager) implementation(projects.domain.legacy) + implementation(projects.domain.txhistory.models) implementation(projects.libs.blockchainSdk) diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt index cdc6d867fc..75f2634213 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyRepository.kt @@ -12,6 +12,8 @@ import com.tangem.datasource.api.tangemTech.YieldSupplyApi import com.tangem.datasource.api.tangemTech.models.YieldSupplyChangeTokenStatusBody import com.tangem.datasource.local.yieldsupply.YieldMarketsStore import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.yield.supply.YieldSupplyRepository @@ -22,6 +24,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext +import timber.log.Timber import java.util.concurrent.ConcurrentHashMap internal class DefaultYieldSupplyRepository( @@ -109,16 +112,21 @@ internal class DefaultYieldSupplyRepository( override suspend fun saveTokenProtocolStatus( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, - yieldSupplyEnterStatus: YieldSupplyEnterStatus, + yieldSupplyEnterStatus: YieldSupplyEnterStatus?, ) { - statusMap["${userWalletId}_${cryptoCurrency.id.value}"] = yieldSupplyEnterStatus + val key = getTokenProtocolStatusKey(userWalletId, cryptoCurrency) + if (yieldSupplyEnterStatus != null) { + statusMap[key] = yieldSupplyEnterStatus + } else { + statusMap.remove(key) + } } override fun getTokenProtocolStatus( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, ): YieldSupplyEnterStatus? { - return statusMap["${userWalletId}_${cryptoCurrency.id.value}"] + return statusMap[getTokenProtocolStatusKey(userWalletId, cryptoCurrency)] } private fun List.enrichNetworkIds(): List { @@ -127,4 +135,44 @@ internal class DefaultYieldSupplyRepository( token.copy(backendId = chainIdMap[token.chainId]) } } + + override suspend fun getTokenPendingStatus( + userWalletId: UserWalletId, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): YieldSupplyEnterStatus? = try { + val cryptoCurrency = cryptoCurrencyStatus.currency + val walletManager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + blockchain = cryptoCurrency.network.toBlockchain(), + derivationPath = cryptoCurrency.network.derivationPath.value, + ) ?: error("Wallet manager not found") + + val pendingTxs = cryptoCurrencyStatus.value.pendingTransactions + + val yieldAddress = walletManager.calculateYieldModuleAddress() + val hasRecentYieldEnterTxs = pendingTxs.hasYieldEnterTransactions(yieldAddress) + val hasRecentYieldExitTxs = pendingTxs.hasYieldExitTransactions() + + when { + hasRecentYieldEnterTxs -> YieldSupplyEnterStatus.Enter + hasRecentYieldExitTxs -> YieldSupplyEnterStatus.Exit + else -> null + } + } catch (e: Exception) { + Timber.e(e, "Failed to get pending yield supply status") + null + } + + private fun Set.hasYieldEnterTransactions(yieldAddress: String) = any { + it.type == TxInfo.TransactionType.YieldSupply.Enter || + it.type == TxInfo.TransactionType.Approve && + (it.interactionAddressType as? TxInfo.InteractionAddressType.Contract)?.address == yieldAddress + } + + private fun Set.hasYieldExitTransactions() = any { + it.type == TxInfo.TransactionType.YieldSupply.Exit + } + + private fun getTokenProtocolStatusKey(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String = + "${userWalletId}_${cryptoCurrency.id.value}" } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt b/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt index 8279bb4189..42da91d04f 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt @@ -160,10 +160,20 @@ sealed class StakingAnalyticsEvent( ), ) - data class UnitializedAddress(val token: String) : StakingAnalyticsEvent( + data class UninitializedAddress(val token: String) : StakingAnalyticsEvent( event = "Notice - Uninitialized Address", params = mapOf(AnalyticsParam.TOKEN_PARAM to token), ) + + data class UninitializedAddressScreen(val token: String) : StakingAnalyticsEvent( + event = "Uninitialized Address Screen", + params = mapOf(AnalyticsParam.TOKEN_PARAM to token), + ) + + data class ButtonActivate(val token: String) : StakingAnalyticsEvent( + event = "Button - Activate", + params = mapOf(AnalyticsParam.TOKEN_PARAM to token), + ) } enum class StakeScreenSource { diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt index 7fbbc6d543..8ab13bbac1 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyRepository.kt @@ -1,6 +1,7 @@ package com.tangem.domain.yield.supply import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.yield.supply.models.YieldMarketToken import com.tangem.domain.yield.supply.models.YieldSupplyEnterStatus @@ -76,7 +77,7 @@ interface YieldSupplyRepository { suspend fun saveTokenProtocolStatus( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, - yieldSupplyEnterStatus: YieldSupplyEnterStatus, + yieldSupplyEnterStatus: YieldSupplyEnterStatus?, ) /** @@ -91,4 +92,20 @@ interface YieldSupplyRepository { * @return the last action intent or null if nothing has been recorded */ fun getTokenProtocolStatus(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): YieldSupplyEnterStatus? + + /** + * Get the pending status of the yield protocol action for the given wallet and currency, if any. + * + * Used to determine whether the UI should display an intermediate "processing" state + * until the protocol status retrieved from backend reflects the change. The value is + * transient (in‑memory only) and is not persisted across app restarts. + * + * @param userWalletId the wallet to query + * @param cryptoCurrencyStatus the currency status to query + * @return the pending action intent or null if nothing has been recorded + */ + suspend fun getTokenPendingStatus( + userWalletId: UserWalletId, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): YieldSupplyEnterStatus? } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt index 97e447a0f5..7e79b03b94 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingClickIntents.kt @@ -9,6 +9,7 @@ import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import java.math.BigDecimal +// TODO split this interface to click intents and other interaction events @Suppress("TooManyFunctions") internal interface StakingClickIntents : AmountScreenClickIntents { @@ -72,5 +73,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun onActivateTonAccountNotificationClick() + fun onActivateTonAccountNotificationShow() + fun onActivateTonAccountClick() } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index aa4707f395..eebecedbcf 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -953,6 +953,11 @@ internal class StakingModel @Inject constructor( } override fun onActivateTonAccountNotificationClick() { + analyticsEventHandler.send( + StakingAnalyticsEvent.UninitializedAddressScreen( + token = cryptoCurrencyStatus.currency.symbol, + ), + ) modelScope.launch { stateController.update( ShowTonInitializeBottomSheetTransformer( @@ -1006,7 +1011,20 @@ internal class StakingModel @Inject constructor( } } + override fun onActivateTonAccountNotificationShow() { + analyticsEventHandler.send( + StakingAnalyticsEvent.UninitializedAddress( + token = cryptoCurrencyStatus.currency.symbol, + ), + ) + } + override fun onActivateTonAccountClick() { + analyticsEventHandler.send( + StakingAnalyticsEvent.ButtonActivate( + token = cryptoCurrencyStatus.currency.symbol, + ), + ) modelScope.launch { tonAccountInitializeTransaction?.let { transaction -> sendTransactionUseCase.invoke( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index d7fac733a7..1b90c1dc49 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -81,5 +81,7 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun onActivateTonAccountNotificationClick() {} + override fun onActivateTonAccountNotificationShow() {} + override fun onActivateTonAccountClick() {} } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt index b4daf2050b..66d3c56eac 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/notifications/AddStakingNotificationsTransformer.kt @@ -295,6 +295,7 @@ internal class AddStakingNotificationsTransformer( val cryptoCurrencyNetworkIdValue = cryptoCurrencyStatusProvider().currency.network.rawId if (isTon(cryptoCurrencyNetworkIdValue) && !isAccountInitialized) { + prevState.clickIntents.onActivateTonAccountNotificationShow() add( StakingNotification.Warning.InitializeTonAccount( onInitializeClick = prevState.clickIntents::onActivateTonAccountNotificationClick, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt index 348a673fd5..0b4b6b742e 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt @@ -189,6 +189,6 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor( } interface ModelCallback { - fun onResult(sendWithSwapUM: SendWithSwapUM) + fun onResult(route: SendWithSwapRoute, sendWithSwapUM: SendWithSwapUM) } } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index ecbfea1277..8d04b3d804 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -425,7 +425,8 @@ internal class SendWithSwapConfirmModel @Inject constructor( val confirmUM = state.confirmUM val isReadyToSend = confirmUM is ConfirmUM.Content && !confirmUM.isTransactionInProcess params.callback.onResult( - state.copy( + route = SendWithSwapRoute.Confirm, + sendWithSwapUM = state.copy( navigationUM = NavigationUM.Content( title = resourceReference(id = R.string.send_with_swap_confirm_title), subtitle = null, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt index 357ffacf9c..3d6968a554 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/model/SendWithSwapModel.kt @@ -116,8 +116,10 @@ internal class SendWithSwapModel @Inject constructor( uiState.update { it.copy(destinationUM = destinationUM) } } - override fun onResult(sendWithSwapUM: SendWithSwapUM) { - uiState.value = sendWithSwapUM + override fun onResult(route: SendWithSwapRoute, sendWithSwapUM: SendWithSwapUM) { + if (currentRoute.value == route) { + uiState.value = sendWithSwapUM + } } override fun onNavigationResult(navigationUM: NavigationUM) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 108d706b1d..e21b971fe1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -36,7 +36,6 @@ import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification -import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles import com.tangem.lib.crypto.BlockchainUtils.isBitcoin import com.tangem.utils.extensions.addIf import com.tangem.utils.extensions.isPositive @@ -62,7 +61,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private val onrampSepaAvailableUseCase: OnrampSepaAvailableUseCase, private val getOnrampCountryUseCase: GetOnrampCountryUseCase, private val notificationsRepository: NotificationsRepository, - private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles, private val accountDependencies: AccountDependencies, ) { @@ -134,7 +132,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( !notificationsRepository.isUserAllowToSubscribeOnPushNotifications(), ) - addYieldSupplyNotifications(flattenCurrencies) + // Remove in first iteration of yield supply feature + // addYieldSupplyNotifications(flattenCurrencies) val hasCriticalOrWarning = any { notification -> notification is WalletNotification.Critical || notification is WalletNotification.Warning @@ -319,14 +318,14 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( ) } - private fun MutableList.addYieldSupplyNotifications( - flattenCurrencies: Lce>, - ) { - addIf( - element = WalletNotification.Warning.YeildSupplyApprove, - condition = flattenCurrencies.hasTokensWithActivatedSupplyWithoutApprove(), - ) - } + // private fun MutableList.addYieldSupplyNotifications( + // flattenCurrencies: Lce>, + // ) { + // addIf( + // element = WalletNotification.Warning.YeildSupplyApprove, + // condition = flattenCurrencies.hasTokensWithActivatedSupplyWithoutApprove(), + // ) + // } private fun MutableList.addWarningNotifications( cardTypesResolver: CardTypesResolver?, @@ -371,13 +370,14 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( return flattenCurrencies.any { it.value is CryptoCurrencyStatus.Unreachable } } - private fun Lce>.hasTokensWithActivatedSupplyWithoutApprove(): Boolean { - val flattenCurrencies = getOrNull(isPartialContentAccepted = false) ?: return false - val yieldSupplyEnabled = yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled - return yieldSupplyEnabled && flattenCurrencies.any { - it.value.yieldSupplyStatus?.isAllowedToSpend == false - } - } + // Remove in first iteration of yield supply feature + // private fun Lce>.hasTokensWithActivatedSupplyWithoutApprove(): Boolean { + // val flattenCurrencies = getOrNull(isPartialContentAccepted = false) ?: return false + // val yieldSupplyEnabled = yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled + // return yieldSupplyEnabled && flattenCurrencies.any { + // it.value.yieldSupplyStatus?.isAllowedToSpend == false + // } + // } private fun MutableList.addRateTheAppNotification( isReadyToShowRating: Boolean, diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/apy/ui/YieldSupplyApyContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/apy/ui/YieldSupplyApyContent.kt index 8d419ff256..ad2186300f 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/apy/ui/YieldSupplyApyContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/apy/ui/YieldSupplyApyContent.kt @@ -12,10 +12,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.InlineTextContent import androidx.compose.foundation.text.appendInlineContent import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.PlaceholderVerticalAlign @@ -114,7 +116,10 @@ internal fun YieldSupplyApyContent( ApyChart( isChartLoading = isLoading, chartComponent = chartComponent, - modifier = Modifier.padding(horizontal = 12.dp), + modifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .background(TangemTheme.colors.background.action) + .padding(12.dp), ) SpacerH16() diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/entity/YieldSupplyUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/entity/YieldSupplyUM.kt index fab5e5aa21..a3ec2d930e 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/entity/YieldSupplyUM.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/entity/YieldSupplyUM.kt @@ -26,6 +26,7 @@ internal sealed class YieldSupplyUM { val rewardsApy: TextReference, val onClick: () -> Unit, val showWarningIcon: Boolean, + val showInfoIcon: Boolean, ) : YieldSupplyUM() @Immutable diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index 2360b55d2d..a6ab368dc5 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.yield.supply.impl.main.model +import android.os.SystemClock import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.common.routing.AppRoute.YieldSupplyPromo @@ -35,9 +36,9 @@ import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.DelayedWork -import com.tangem.utils.transformer.update import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn +import com.tangem.utils.transformer.update import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* @@ -46,7 +47,7 @@ import timber.log.Timber import javax.inject.Inject import kotlin.properties.Delegates -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @ModelScoped internal class YieldSupplyModel @Inject constructor( paramsContainer: ParamsContainer, @@ -72,6 +73,7 @@ internal class YieldSupplyModel @Inject constructor( val bottomSheetNavigation: SlotNavigation = SlotNavigation() + private val handleNavigation = params.handleNavigation private val cryptoCurrency = params.cryptoCurrency var userWallet: UserWallet by Delegates.notNull() @@ -89,10 +91,17 @@ internal class YieldSupplyModel @Inject constructor( private var lastYieldSupplyStatus: YieldSupplyStatus? = null private val fetchCurrencyJobHolder = JobHolder() + private var lastStatusCheckTimestamp = 0L + init { checkIfYieldSupplyIsAvailable() - params.handleNavigation?.let { handle -> - if (handle) { + + val protocolStatus = yieldSupplyRepository.getTokenProtocolStatus( + userWalletId = params.userWalletId, + cryptoCurrency = cryptoCurrency, + ) + if (handleNavigation != null && protocolStatus == null) { + if (handleNavigation) { modelScope.launch { delay(timeMillis = 1000) bottomSheetNavigation.activate(Unit) @@ -193,48 +202,88 @@ internal class YieldSupplyModel @Inject constructor( } @Suppress("MaximumLineLength") - private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) { + private fun onCryptoCurrencyStatusUpdated(cryptoCurrencyStatus: CryptoCurrencyStatus) = modelScope.launch { val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus val tokenProtocolStatus = yieldSupplyRepository.getTokenProtocolStatus( userWallet.walletId, cryptoCurrency, ) + val tokenPendingStatus = yieldSupplyRepository.getTokenPendingStatus( + userWallet.walletId, + cryptoCurrencyStatus, + ) + val isActive = yieldSupplyStatus?.isActive == true val isCryptoCurrencyStatusFromCache = cryptoCurrencyStatus.value.sources.networkSource != StatusSource.ACTUAL val processing = uiState.value is YieldSupplyUM.Processing Timber.d( - "currentUiState ${uiState.value.javaClass} \n" + - "yieldSupplyStatus $yieldSupplyStatus" + + "YIELD " + + "yieldSupplyStatus $yieldSupplyStatus " + "tokenProtocolStatus $tokenProtocolStatus " + + "tokenPendingStatus $tokenPendingStatus " + "isActive $isActive " + "processing $processing " + "isCryptoCurrencyStatusFromCache $isCryptoCurrencyStatusFromCache", ) if (isCryptoCurrencyStatusFromCache && processing) { - return + return@launch } when { - !isActive && tokenProtocolStatus == YieldSupplyEnterStatus.Enter -> { - uiState.update { YieldSupplyUM.Processing.Enter } - fetchCurrencyWithDelay() + tokenProtocolStatus != null && tokenPendingStatus != null -> { + showProcessing(tokenPendingStatus) + lastStatusCheckTimestamp = 0L } - isActive && tokenProtocolStatus == YieldSupplyEnterStatus.Exit -> { - uiState.update { YieldSupplyUM.Processing.Exit } - fetchCurrencyWithDelay() + tokenProtocolStatus == YieldSupplyEnterStatus.Exit && isActive || + tokenProtocolStatus == YieldSupplyEnterStatus.Enter && !isActive -> { + if (lastStatusCheckTimestamp != 0L) { + if (SystemClock.elapsedRealtime() - lastStatusCheckTimestamp > MAX_STATUS_CHECK_LIMIT) { + loadStatus(cryptoCurrencyStatus) + lastStatusCheckTimestamp = 0L + } else { + showProcessing(tokenProtocolStatus) + } + } else { + showProcessing(tokenProtocolStatus) + lastStatusCheckTimestamp = SystemClock.elapsedRealtime() + } } else -> { + loadStatus(cryptoCurrencyStatus) + lastStatusCheckTimestamp = 0L + } + } + } + + private fun showProcessing(status: YieldSupplyEnterStatus) { + uiState.update { + when (status) { + YieldSupplyEnterStatus.Enter -> YieldSupplyUM.Processing.Enter + YieldSupplyEnterStatus.Exit -> YieldSupplyUM.Processing.Exit + } + } + fetchCurrencyWithDelay() + } + + private fun loadStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) { + val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus + modelScope + .launch { + yieldSupplyRepository.saveTokenProtocolStatus( + userWalletId = userWallet.walletId, + cryptoCurrency = cryptoCurrency, + yieldSupplyEnterStatus = null, + ) sendInfoAboutProtocolStatus(cryptoCurrencyStatus) - if (isActive) { + if (yieldSupplyStatus?.isActive == true) { loadActiveState( cryptoCurrencyStatus = cryptoCurrencyStatus, - yieldSupplyStatus = requireNotNull(yieldSupplyStatus), + yieldSupplyStatus = yieldSupplyStatus, ) } else { loadTokenStatus() } } - } } private fun fetchCurrencyWithDelay() { @@ -253,8 +302,8 @@ internal class YieldSupplyModel @Inject constructor( private fun loadActiveState(cryptoCurrencyStatus: CryptoCurrencyStatus, yieldSupplyStatus: YieldSupplyStatus) { val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return - val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend || - cryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied() + val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend + val showInfoIcon = cryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied() if (!yieldSupplyStatus.isAllowedToSpend) { analyticsEventsHandler.send( YieldSupplyAnalytics.NoticeApproveNeeded( @@ -282,6 +331,7 @@ internal class YieldSupplyModel @Inject constructor( ), onClick = ::onActiveClick, showWarningIcon = showWarningIcon, + showInfoIcon = showInfoIcon, apy = tokenStatus.apy.toString(), ) } @@ -298,6 +348,7 @@ internal class YieldSupplyModel @Inject constructor( rewardsApy = TextReference.EMPTY, onClick = ::onActiveClick, showWarningIcon = showWarningIcon, + showInfoIcon = showInfoIcon, apy = "", ) } @@ -328,5 +379,6 @@ internal class YieldSupplyModel @Inject constructor( private companion object { const val PROCESSING_UPDATE_DELAY = 10_000L + const val MAX_STATUS_CHECK_LIMIT = 10_000L } } \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/ui/YieldSupplyBlockContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/ui/YieldSupplyBlockContent.kt index 1cbf8f1cfe..df2c84d279 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/ui/YieldSupplyBlockContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/ui/YieldSupplyBlockContent.kt @@ -149,12 +149,21 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier = ) } SpacerW8() - AnimatedVisibility(supplyUM.showWarningIcon) { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_alert_triangle_20), - contentDescription = null, - tint = TangemTheme.colors.icon.attention, - ) + AnimatedContent( + targetState = supplyUM, + ) { currentState -> + when { + currentState.showWarningIcon -> Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_alert_triangle_20), + contentDescription = null, + tint = TangemTheme.colors.icon.attention, + ) + currentState.showInfoIcon -> Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_alert_circle_red_20), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + } } Icon( imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24), @@ -355,6 +364,7 @@ private class PreviewProvider : PreviewParameterProvider { onClick = {}, apy = "5.1", showWarningIcon = false, + showInfoIcon = true, ), YieldSupplyUM.Content( title = stringReference("Aave lending is active "), @@ -363,6 +373,7 @@ private class PreviewProvider : PreviewParameterProvider { onClick = {}, apy = "5.1", showWarningIcon = true, + showInfoIcon = false, ), YieldSupplyUM.Loading, YieldSupplyUM.Processing.Enter, diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt index 12359bae82..dc7127e226 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt @@ -4,13 +4,16 @@ import android.content.res.Configuration import androidx.annotation.DrawableRes import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector @@ -57,7 +60,7 @@ internal fun YieldSupplyPromoContent( onClick = clickIntents::onUrlClick, ) PrimaryButton( - text = stringResourceSafe(R.string.yield_module_start_earning), + text = stringResourceSafe(R.string.common_continue), onClick = clickIntents::onStartEarningClick, modifier = Modifier .padding( @@ -106,7 +109,7 @@ private fun ColumnScope.Content(yieldSupplyPromoUM: YieldSupplyPromoUM, clickInt text = yieldSupplyPromoUM.subtitle, style = LabelStyle.REGULAR, icon = R.drawable.ic_information_24, - onIconClick = clickIntents::onApyInfoClick, + onClick = clickIntents::onApyInfoClick, ), ) SpacerH32() @@ -131,7 +134,11 @@ private fun YieldStatusAppBar(onBackClick: () -> Unit, onHowItWorksClick: () -> imageVector = ImageVector.vectorResource(R.drawable.ic_back_24), contentDescription = null, tint = TangemTheme.colors.icon.primary1, - modifier = Modifier.clickable(onClick = onBackClick), + modifier = Modifier.clickable( + onClick = onBackClick, + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(bounded = false), + ), ) SpacerWMax() Text( diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt index 070ec52cb7..414cd7a29d 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt @@ -195,7 +195,7 @@ internal class YieldSupplyActiveModel @Inject constructor( blockchain = cryptoCurrency.network.name, ), ) - NotificationUM.Warning.YieldSupplyNotAllAmountSupplied( + NotificationUM.Info.YieldSupplyNotAllAmountSupplied( formattedAmount = formattedAmount, symbol = cryptoCurrency.symbol, ) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/ui/YieldSupplyActiveContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/ui/YieldSupplyActiveContent.kt index 0654981fee..f303d4ae1e 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/ui/YieldSupplyActiveContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/ui/YieldSupplyActiveContent.kt @@ -26,7 +26,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import com.tangem.common.ui.notifications.NotificationUM -import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.SpacerH4 +import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.decompose.ComposableContentComponent @@ -61,7 +64,7 @@ internal fun YieldSupplyActiveContent( .padding(12.dp), ) { CurrentApy(state.apy) - chartComponent.Content(Modifier.padding(bottom = 12.dp)) + chartComponent.Content(Modifier) } AnimatedVisibility(state.notifications.isNotEmpty()) { @@ -71,6 +74,11 @@ internal fun YieldSupplyActiveContent( Notification( config = notificationUM.config, containerColor = TangemTheme.colors.background.action, + iconTint = if (notificationUM is NotificationUM.Info) { + TangemTheme.colors.icon.accent + } else { + null + }, ) } } @@ -104,7 +112,7 @@ internal fun YieldSupplyActiveContent( @Composable private fun CurrentApy(apy: TextReference?, modifier: Modifier = Modifier) { - Column(modifier = modifier.padding(vertical = 12.dp)) { + Column(modifier = modifier) { Text( modifier = Modifier, text = stringResourceSafe(R.string.yield_module_earn_sheet_current_apy_title), @@ -156,7 +164,11 @@ private fun YieldSupplyActiveMyFunds( .clip(RoundedCornerShape(16.dp)) .background(TangemTheme.colors.background.action) .fillMaxWidth() - .padding(12.dp), + .padding( + top = 12.dp, + start = 12.dp, + end = 12.dp, + ), ) { Text( text = stringResourceSafe(R.string.yield_module_earn_sheet_my_funds_title), diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/feepolicy/ui/YieldSupplyFeePolicyContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/feepolicy/ui/YieldSupplyFeePolicyContent.kt index ec32b8e52b..f00297a2f9 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/feepolicy/ui/YieldSupplyFeePolicyContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/feepolicy/ui/YieldSupplyFeePolicyContent.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -91,7 +92,7 @@ internal fun YieldSupplyFeePolicyContent( YieldSupplyFeeUM.Loading -> null } YieldSupplyFeeRow( - title = resourceReference(R.string.yield_module_fee_policy_sheet_current_fee_title), + title = resourceReference(R.string.common_estimated_fee), value = currentFee, modifier = Modifier.padding(vertical = 12.dp), ) @@ -100,6 +101,10 @@ internal fun YieldSupplyFeePolicyContent( YieldSupplyFeeUM.Error -> stringReference(StringsSigns.DASH_SIGN) YieldSupplyFeeUM.Loading -> null } + HorizontalDivider( + thickness = 0.5.dp, + color = TangemTheme.colors.stroke.primary, + ) YieldSupplyFeeRow( title = resourceReference(R.string.yield_module_fee_policy_sheet_max_fee_title), value = maxFee, diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt index e890ae8bc5..95e7c4d6d1 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt @@ -144,7 +144,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor( val transactionListData = yieldSupplyStartEarningUseCase( userWalletId = userWallet.walletId, cryptoCurrencyStatus = cryptoCurrencyStatus, - maxNetworkFee = maxFee.nativeMaxFee, + maxNetworkFee = maxFee.tokenMaxFee, ).getOrNull() if (transactionListData == null) { diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 48711f2377..e06f0759db 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1301" +tangemBlockchainSdk = "develop-1306" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-564" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt index 88e9d25480..e60739fd69 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/AppExtensionConfigurations.kt @@ -4,6 +4,7 @@ import com.android.build.gradle.AppExtension import com.tangem.plugin.configuration.model.AppConfig import com.tangem.plugin.configuration.model.BuildType import com.tangem.plugin.configuration.utils.BuildConfigFieldFactory +import com.tangem.plugin.configuration.utils.VersionNameProvider import org.gradle.api.Project import com.android.build.gradle.internal.dsl.BuildType as AndroidBuildType @@ -29,11 +30,11 @@ private fun AppExtension.configureDefaultConfig(project: Project) { AppConfig.versionCode } - versionName = if (project.hasProperty("versionName")) { - project.property("versionName") as String - } else { - AppConfig.versionName - } + // Get version name from property, git branch, or default config + val versionNameProvider = VersionNameProvider(project) + versionName = versionNameProvider.getVersionName() + + project.logger.lifecycle("Resolved versionName: $versionName") buildFeatures.buildConfig = true diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt index 5ddd7ba12d..25a86d101f 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/model/AppConfig.kt @@ -3,8 +3,6 @@ package com.tangem.plugin.configuration.model internal object AppConfig { const val packageName = "com.tangem.wallet" const val versionCode = 1 - const val versionName = "1.0.0-SNAPSHOT" - // const val versionName = "100.0.0-SNAPSHOT" //TODO: [REDACTED_JIRA] const val minSdkVersion = 24 const val targetSdkVersion = 35 const val compileSdkVersion = 35 diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/utils/VersionNameProvider.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/utils/VersionNameProvider.kt new file mode 100644 index 0000000000..905d2f12e4 --- /dev/null +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/utils/VersionNameProvider.kt @@ -0,0 +1,105 @@ +package com.tangem.plugin.configuration.utils + +import org.gradle.api.Project +import org.gradle.api.provider.Provider + +/** + * Provides version name based on current git branch + */ +internal class VersionNameProvider( + private val project: Project, +) { + + /** + * Get version name for current branch. + * If -PversionName is provided, uses that value. + * Otherwise, derives version from git branch name. + */ + fun getVersionName(): String { + // Check if versionName is provided as gradle property + if (project.hasProperty("versionName")) { + return project.property("versionName") as String + } + + // Get current branch name using Provider API (configuration cache compatible) + val currentBranch = getCurrentBranchProvider().get() + + // Try to extract version from branch name (releases/X.Y) + val versionFromBranch = extractVersionFromBranch(currentBranch) + if (versionFromBranch != null) { + return versionFromBranch + } + + // For other branches (develop, feature/*), find latest release branch and increment minor + val latestReleaseBranch = findLatestReleaseBranch() + if (latestReleaseBranch != null) { + val versionFromLatest = extractVersionFromBranch(latestReleaseBranch) + if (versionFromLatest != null) { + return incrementMinorVersion(versionFromLatest) + } + } + + // Fallback to default if nothing works + return "1.0.0-SNAPSHOT" + } + + private fun getCurrentBranchProvider(): Provider { + return project.providers.exec { + commandLine("git", "rev-parse", "--abbrev-ref", "HEAD") + }.standardOutput.asText.map { it.trim() } + } + + private fun findLatestReleaseBranch(): String? { + val scriptPath = project.rootProject.file("tangem-android-tools/CI/shell_scripts/find-latest-release-branch.sh") + if (!scriptPath.exists()) { + project.logger.warn("Script not found: $scriptPath") + return null + } + + val currentBranch = getCurrentBranchProvider().get() + val outputFile = project.rootProject.file("find-latest-release-branch.output") + + return try { + project.providers.exec { + commandLine("sh", scriptPath.absolutePath, currentBranch) + }.standardOutput.asText.get() + + if (!outputFile.exists()) { + project.logger.warn("Script output file not found") + return null + } + + outputFile.readText().trim().also { result -> + project.logger.lifecycle("Found latest release branch: $result") + outputFile.delete() + } + } catch (e: Exception) { + project.logger.warn("Failed to execute script: ${e.message}") + outputFile.delete() + null + } + } + + private fun extractVersionFromBranch(branch: String): String? { + val regex = Regex("""^releases/(\d+)\.(\d+)(?:\.(\d+))?$""") + val matchResult = regex.find(branch) ?: return null + + val major = matchResult.groupValues[1] + val minor = matchResult.groupValues[2] + val patch = matchResult.groupValues[3].ifEmpty { "0" } + + return "$major.$minor.$patch" + } + + private fun incrementMinorVersion(version: String): String { + val parts = version.split(".") + if (parts.size < 2) return version + + val major = parts[0] + val minor = parts[1].toIntOrNull() ?: return version + val newMinor = minor + 1 + + return "$major.$newMinor.0" + } + +} \ No newline at end of file