Updated on 2026-08-14
This commit is contained in:
parent
6018cf0b71
commit
f7b10f3ed5
31 changed files with 446 additions and 105 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -53,3 +53,4 @@ app/src/external/google-services.json
|
|||
|
||||
# Kotlin Plugin
|
||||
.kotlin/
|
||||
find-latest-release-branch.output
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@
|
|||
<string name="common_free">無料</string>
|
||||
<string name="common_from">送信元</string>
|
||||
<string name="common_generate_addresses">アドレスを同期する</string>
|
||||
<string name="common_get_started">はじめる</string>
|
||||
<string name="common_get_started">もっと詳しく</string>
|
||||
<string name="common_get_token">トークンを取得</string>
|
||||
<string name="common_go_to_provider">プロバイダーへ移動</string>
|
||||
<string name="common_go_to_token">トークンへ移動</string>
|
||||
|
|
|
|||
|
|
@ -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,7 +96,9 @@ fun Label(state: LabelUM, modifier: Modifier = Modifier) {
|
|||
imageVector = ImageVector.vectorResource(wrappedIcon),
|
||||
tint = iconColor,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp).clickable(
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = ripple(bounded = false),
|
||||
onClick = { state.onIconClick?.invoke() },
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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<YieldMarketToken>.enrichNetworkIds(): List<YieldMarketToken> {
|
||||
|
|
@ -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<TxInfo>.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<TxInfo>.hasYieldExitTransactions() = any {
|
||||
it.type == TxInfo.TransactionType.YieldSupply.Exit
|
||||
}
|
||||
|
||||
private fun getTokenProtocolStatusKey(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String =
|
||||
"${userWalletId}_${cryptoCurrency.id.value}"
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -81,5 +81,7 @@ internal object StakingClickIntentsStub : StakingClickIntents {
|
|||
|
||||
override fun onActivateTonAccountNotificationClick() {}
|
||||
|
||||
override fun onActivateTonAccountNotificationShow() {}
|
||||
|
||||
override fun onActivateTonAccountClick() {}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -189,6 +189,6 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor(
|
|||
}
|
||||
|
||||
interface ModelCallback {
|
||||
fun onResult(sendWithSwapUM: SendWithSwapUM)
|
||||
fun onResult(route: SendWithSwapRoute, sendWithSwapUM: SendWithSwapUM)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -116,9 +116,11 @@ internal class SendWithSwapModel @Inject constructor(
|
|||
uiState.update { it.copy(destinationUM = destinationUM) }
|
||||
}
|
||||
|
||||
override fun onResult(sendWithSwapUM: SendWithSwapUM) {
|
||||
override fun onResult(route: SendWithSwapRoute, sendWithSwapUM: SendWithSwapUM) {
|
||||
if (currentRoute.value == route) {
|
||||
uiState.value = sendWithSwapUM
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNavigationResult(navigationUM: NavigationUM) {
|
||||
uiState.update { it.copy(navigationUM = navigationUM) }
|
||||
|
|
|
|||
|
|
@ -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<WalletNotification>.addYieldSupplyNotifications(
|
||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.Warning.YeildSupplyApprove,
|
||||
condition = flattenCurrencies.hasTokensWithActivatedSupplyWithoutApprove(),
|
||||
)
|
||||
}
|
||||
// private fun MutableList<WalletNotification>.addYieldSupplyNotifications(
|
||||
// flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
// ) {
|
||||
// addIf(
|
||||
// element = WalletNotification.Warning.YeildSupplyApprove,
|
||||
// condition = flattenCurrencies.hasTokensWithActivatedSupplyWithoutApprove(),
|
||||
// )
|
||||
// }
|
||||
|
||||
private fun MutableList<WalletNotification>.addWarningNotifications(
|
||||
cardTypesResolver: CardTypesResolver?,
|
||||
|
|
@ -371,13 +370,14 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
return flattenCurrencies.any { it.value is CryptoCurrencyStatus.Unreachable }
|
||||
}
|
||||
|
||||
private fun Lce<TokenListError, List<CryptoCurrencyStatus>>.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<TokenListError, List<CryptoCurrencyStatus>>.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<WalletNotification>.addRateTheAppNotification(
|
||||
isReadyToShowRating: Boolean,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ internal sealed class YieldSupplyUM {
|
|||
val rewardsApy: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
val showWarningIcon: Boolean,
|
||||
val showInfoIcon: Boolean,
|
||||
) : YieldSupplyUM()
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -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<Unit> = 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,49 +202,89 @@ 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" +
|
||||
"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
|
||||
}
|
||||
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()
|
||||
}
|
||||
isActive && tokenProtocolStatus == YieldSupplyEnterStatus.Exit -> {
|
||||
uiState.update { YieldSupplyUM.Processing.Exit }
|
||||
fetchCurrencyWithDelay()
|
||||
}
|
||||
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() {
|
||||
coroutineScope.launch(dispatchers.io) {
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -149,12 +149,21 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier =
|
|||
)
|
||||
}
|
||||
SpacerW8()
|
||||
AnimatedVisibility(supplyUM.showWarningIcon) {
|
||||
Icon(
|
||||
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<YieldSupplyUM> {
|
|||
onClick = {},
|
||||
apy = "5.1",
|
||||
showWarningIcon = false,
|
||||
showInfoIcon = true,
|
||||
),
|
||||
YieldSupplyUM.Content(
|
||||
title = stringReference("Aave lending is active "),
|
||||
|
|
@ -363,6 +373,7 @@ private class PreviewProvider : PreviewParameterProvider<YieldSupplyUM> {
|
|||
onClick = {},
|
||||
apy = "5.1",
|
||||
showWarningIcon = true,
|
||||
showInfoIcon = false,
|
||||
),
|
||||
YieldSupplyUM.Loading,
|
||||
YieldSupplyUM.Processing.Enter,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
blockchain = cryptoCurrency.network.name,
|
||||
),
|
||||
)
|
||||
NotificationUM.Warning.YieldSupplyNotAllAmountSupplied(
|
||||
NotificationUM.Info.YieldSupplyNotAllAmountSupplied(
|
||||
formattedAmount = formattedAmount,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 ^
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
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"
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue