diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index e0539d9751..3eedac2f33 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -280,7 +280,7 @@
Primärring
Passphrase
Einfügen
- Datenschutzbestimmungen
+ Datenschutzrichtlinie
%1$s-%2$s
%1$s — %2$s
Weiterlesen
@@ -317,6 +317,7 @@
Unterstützte Netzwerke
Tauschen
Tangem
+ Tangem Wallet
Allgemeine Geschäftsbedingungen
Nutzungsbedingungen
Heute
@@ -418,7 +419,7 @@
Getauscht von %s
Besuche die Website des Anbieters, um dein Geld zurückzuerhalten
Fehler beim Vorgang durch Anbieter
- Dein Wechsel dauert länger als erwartet. Wende Dich für Hilfe bitte an den Support Deines Anbieters.
+ Dein Swap dauert länger als üblich, aber Dein Geld ist absolut sicher und wird geliefert. Bei Fragen kannst Du Dich an das Support-Team des Anbieters wenden.
Lange Transaktionszeit
Der Transaktionsbetrag wurde aufgrund von OKX- oder Bridge-Regeln in %1$s auf deine Wallet zurückerstattet. %2$s
Der Betrag wurde in %1$s (%2$s Netzwerk) zurückerstattet.
diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml
index 50f599d85a..de1675f6ef 100644
--- a/core/res/src/main/res/values-ja/strings.xml
+++ b/core/res/src/main/res/values-ja/strings.xml
@@ -197,6 +197,7 @@
カメラへのアクセスを許可していません。プライバシー設定を調整してください。
キャンセル
変更
+ アカウントを選択
アクションを選択
ネットワークを選択
トークンを選択
@@ -1247,6 +1248,14 @@
受け取る
トークンを選択
利用不可
+ カードをGET
+ ウォレットに追加して、どこでもスマートフォンで支払いができます。
+ Apple Pay & Google Pay
+ USDC残高を使用して、毎日のお買い物をシームレスに支払います。
+ 日常の買い物
+ カードの詳細は保護されており、アプリ内で完全に制御できます。
+ 内蔵セキュリティ
+ 無料のCryptoカード\nを数分で入手しましょう
これは私のウォレットです
残高非表示
残高表示
@@ -1634,7 +1643,6 @@
現在のAPY
私の資金
あなたの%1$sはAaveに預けられ、利息が付きます。あなたは%2$sトークンを保有しており、これは残高を表し、時間の経過とともに増加します。入金すると、資金はAaveに預け入れられ、取引手数料を差し引いた利息が付きます。
- アクティブ
利回りを得る
総収益
Aaveへの送金
@@ -1656,7 +1664,7 @@
使い方
Aaveは世界中で何百万人もの人々に信頼されています。総貸付額は104億ドルです。
分散型・自己管理型
- サービスを使用することにより、プロバイダーに同意したものとみなされます
+ サービスを利用することにより、プロバイダー\n %1$sおよび%2$sに同意したことになります
年間%s%%の収益
Aave • 変動金利
Aave
@@ -1670,6 +1678,7 @@
%sはAaveに供給され、すぐに利用可能になります
手数料ポリシーを見る
次回の入金は自動的にAaveに供給されます。
+ アクティブ
収益を停止する
オフにすると、Aaveから資金が引き出され、ウォレットの%sに戻され、報酬の獲得が停止されます。
出金金額からネットワーク手数料が差し引かれます。
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index fb4b2b7a3b..f2dc5313d9 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -1623,6 +1623,7 @@
We\'ve got some kind of problem
All dApps disconnected
Allow to spend
+ By approving, you allow dApp or Smart contract to use tokens in future transactions.
Address
Connect
Loading
@@ -1711,7 +1712,6 @@
Current APY
My Funds
Your %1$s is now deposited in Aave and earning interest. You hold a%2$s token, which represents your balance and grows over time. When you top up, funds go to Aave to earn interest, minus a transaction fee.
- Active
Earn
Total earnings
Transfers to Aave
@@ -1733,7 +1733,7 @@
How it works?
Aave is trusted by millions worldwide. Total lended value is $10.4B.
Decentralized and self-custodial
- By using service, you agree with provider %1$s and %2$s
+ By using service, you agree with provider\n%1$s and %2$s
Earn %s%% yearly
Aave • Variable Interest Rate
Aave
@@ -1747,6 +1747,8 @@
Your %s will be supplied to Aave and will stay instantly available
See fee policy
Your next deposits will be automatically supplied to Aave.
+ Active
+ Paused
Stop earning
Turning off will withdraw your funds from Aave, return them to %s in your wallet, and stop earning rewards.
The network fee will be deducted from the amount you withdraw.
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt b/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt
index 028be341c1..4c44d3992a 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt
@@ -2,12 +2,13 @@ package com.tangem.core.ui.components.containers
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveAnnotatedReference
import com.tangem.core.ui.res.TangemTheme
@@ -17,14 +18,14 @@ import com.tangem.core.ui.res.TangemTheme
*
* @param modifier of component
* @param footer text
- * @param footerTopPadding padding between footer and field
+ * @param paddingValues padding between footer and field
* @param content field content
*/
@Composable
fun FooterContainer(
modifier: Modifier = Modifier,
footer: TextReference? = null,
- footerTopPadding: Dp = TangemTheme.dimens.spacing8,
+ paddingValues: PaddingValues = PaddingValues(top = 8.dp),
content: @Composable () -> Unit,
) {
Column(modifier = modifier) {
@@ -36,7 +37,7 @@ fun FooterContainer(
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
- .padding(top = footerTopPadding),
+ .padding(paddingValues),
)
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularContentComponent.kt b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularContentComponent.kt
new file mode 100644
index 0000000000..15a24d2001
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/decompose/ComposableModularContentComponent.kt
@@ -0,0 +1,37 @@
+package com.tangem.core.ui.decompose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Modifier
+
+@Stable
+interface ComposableModularContentComponent {
+
+ @Composable
+ fun Title()
+
+ @Composable
+ fun Content(modifier: Modifier)
+
+ @Composable
+ fun Footer()
+}
+
+fun getEmptyComposableModularContentComponent() = EmptyComposableModularContentComponent
+
+object EmptyComposableModularContentComponent : ComposableModularContentComponent {
+ @Composable
+ override fun Title() {
+ /* no-op */
+ }
+
+ @Composable
+ override fun Content(modifier: Modifier) {
+ /* no-op */
+ }
+
+ @Composable
+ override fun Footer() {
+ /* no-op */
+ }
+}
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_aave_36.xml b/core/ui/src/main/res/drawable/ic_aave_36.xml
new file mode 100644
index 0000000000..310afe5b24
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_aave_36.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEstimateEnterFeeUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEstimateEnterFeeUseCase.kt
index 0c7dd8bd8c..6c84240db5 100644
--- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEstimateEnterFeeUseCase.kt
+++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEstimateEnterFeeUseCase.kt
@@ -1,13 +1,16 @@
package com.tangem.domain.yield.supply.usecase
import arrow.core.Either
+import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
+import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.error.FeeErrorResolver
import com.tangem.domain.transaction.error.GetFeeError
+import com.tangem.utils.extensions.isSingleItem
class YieldSupplyEstimateEnterFeeUseCase(
private val feeRepository: FeeRepository,
@@ -18,19 +21,36 @@ class YieldSupplyEstimateEnterFeeUseCase(
cryptoCurrency: CryptoCurrency,
transactionDataList: List,
): Either> = Either.catch {
- transactionDataList.mapIndexed { index, transaction ->
- val fee = feeRepository.calculateFee(
- userWallet = userWallet,
- cryptoCurrency = cryptoCurrency,
- transactionData = transaction,
- ).normal
+ val withCalculatedFee = transactionDataList.filter {
+ (it.extras as? EthereumTransactionExtras)?.callData !is EthereumYieldSupplyEnterCallData
+ }.map { transaction ->
+ transaction.copy(
+ fee = feeRepository.calculateFee(
+ userWallet = userWallet,
+ cryptoCurrency = cryptoCurrency,
+ transactionData = transaction,
+ ).normal,
+ )
+ }
- if (index == transactionDataList.lastIndex) { // todo yield supply replace with check for contract
- transaction.copy(fee = fee.fixFee(cryptoCurrency))
+ val withEstimatedCalculatedFee = transactionDataList.filter {
+ (it.extras as? EthereumTransactionExtras)?.callData is EthereumYieldSupplyEnterCallData
+ }.map { transaction ->
+ if (transactionDataList.isSingleItem()) {
+ transaction.copy(
+ fee = feeRepository.calculateFee(
+ userWallet = userWallet,
+ cryptoCurrency = cryptoCurrency,
+ transactionData = transaction,
+ ).normal,
+ )
} else {
- transaction.copy(fee = fee)
+ transaction.copy(fee = withCalculatedFee.first().fee?.fixFee(cryptoCurrency))
}
}
+
+ // Transactions order must be preserved
+ withCalculatedFee + withEstimatedCalculatedFee
}.mapLeft(feeErrorResolver::resolve)
private fun Fee.fixFee(cryptoCurrency: CryptoCurrency) = when (this) {
diff --git a/features/yield-supply/impl/build.gradle.kts b/features/yield-supply/impl/build.gradle.kts
index 95021510f8..0944ea2a33 100644
--- a/features/yield-supply/impl/build.gradle.kts
+++ b/features/yield-supply/impl/build.gradle.kts
@@ -42,6 +42,7 @@ dependencies {
implementation(projects.domain.tokens)
implementation(projects.domain.transaction.models)
implementation(projects.domain.transaction)
+ implementation(projects.domain.yieldSupply)
/** Compose */
implementation(deps.compose.foundation)
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/entity/YieldSupplyFeeUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/entity/YieldSupplyFeeUM.kt
new file mode 100644
index 0000000000..9c30cb4f8e
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/entity/YieldSupplyFeeUM.kt
@@ -0,0 +1,26 @@
+package com.tangem.features.yield.supply.impl.common.entity
+
+import androidx.compose.runtime.Immutable
+import com.tangem.blockchain.common.TransactionData
+import com.tangem.core.ui.components.currency.icon.CurrencyIconState
+import com.tangem.core.ui.extensions.TextReference
+import kotlinx.collections.immutable.ImmutableList
+
+@Immutable
+internal sealed class YieldSupplyFeeUM {
+ data object Loading : YieldSupplyFeeUM()
+ data object Error : YieldSupplyFeeUM()
+ data class Content(
+ val transactionDataList: ImmutableList,
+ val feeValue: TextReference,
+ ) : YieldSupplyFeeUM()
+}
+
+internal data class YieldSupplyActionUM(
+ val title: TextReference,
+ val subtitle: TextReference,
+ val footer: TextReference,
+ val currencyIconState: CurrencyIconState,
+ val yieldSupplyFeeUM: YieldSupplyFeeUM,
+ val isPrimaryButtonEnabled: Boolean,
+)
\ No newline at end of file
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyActionContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyActionContent.kt
new file mode 100644
index 0000000000..98746a3d45
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyActionContent.kt
@@ -0,0 +1,147 @@
+package com.tangem.features.yield.supply.impl.common.ui
+
+import android.content.res.Configuration
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+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.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import androidx.compose.ui.unit.dp
+import com.tangem.core.ui.components.*
+import com.tangem.core.ui.components.containers.FooterContainer
+import com.tangem.core.ui.components.currency.icon.CurrencyIcon
+import com.tangem.core.ui.components.currency.icon.CurrencyIconState
+import com.tangem.core.ui.extensions.*
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemThemePreview
+import com.tangem.features.yield.supply.impl.R
+import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
+import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
+import com.tangem.utils.StringsSigns
+import kotlinx.collections.immutable.persistentListOf
+
+@Composable
+internal fun YieldSupplyActionContent(
+ yieldSupplyActionUM: YieldSupplyActionUM,
+ modifier: Modifier = Modifier,
+ iconContent: @Composable ColumnScope.() -> Unit,
+) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = modifier.padding(bottom = 16.dp, start = 16.dp, end = 16.dp),
+ ) {
+ iconContent()
+ SpacerH24()
+ Text(
+ text = yieldSupplyActionUM.title.resolveReference(),
+ style = TangemTheme.typography.h3,
+ color = TangemTheme.colors.text.primary1,
+ )
+ SpacerH8()
+ Text(
+ text = yieldSupplyActionUM.subtitle.resolveReference(),
+ style = TangemTheme.typography.body2,
+ color = TangemTheme.colors.text.secondary,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(horizontal = 16.dp),
+ )
+ SpacerH24()
+ FooterContainer(
+ footer = yieldSupplyActionUM.footer,
+ paddingValues = PaddingValues(
+ start = 12.dp,
+ end = 12.dp,
+ top = 8.dp,
+ ),
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(14.dp))
+ .background(TangemTheme.colors.background.action)
+ .padding(horizontal = 16.dp, vertical = 12.dp),
+ ) {
+ Text(
+ text = stringResourceSafe(R.string.common_network_fee_title),
+ style = TangemTheme.typography.body1,
+ color = TangemTheme.colors.text.primary1,
+ )
+ SpacerWMax()
+ when (val fee = yieldSupplyActionUM.yieldSupplyFeeUM) {
+ is YieldSupplyFeeUM.Content -> Text(
+ text = fee.feeValue.resolveReference(),
+ style = TangemTheme.typography.body1,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ YieldSupplyFeeUM.Error -> Text(
+ text = stringResourceSafe(R.string.common_fee_error),
+ style = TangemTheme.typography.body1,
+ color = TangemTheme.colors.text.attention,
+ )
+ YieldSupplyFeeUM.Loading -> {
+ TextShimmer(
+ style = TangemTheme.typography.body1,
+ text = stringResourceSafe(R.string.common_fee_error),
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+// region Preview
+@Composable
+@Preview(showBackground = true, widthDp = 344)
+@Preview(showBackground = true, widthDp = 344, uiMode = Configuration.UI_MODE_NIGHT_YES)
+private fun YieldSupplyActionContent_Preview(
+ @PreviewParameter(YieldSupplyActionContentPreviewProvider::class) params: YieldSupplyActionUM,
+) {
+ TangemThemePreview {
+ YieldSupplyActionContent(
+ yieldSupplyActionUM = params,
+ modifier = Modifier.background(TangemTheme.colors.background.tertiary),
+ ) {
+ CurrencyIcon(
+ state = CurrencyIconState.Loading,
+ shouldDisplayNetwork = false,
+ iconSize = 48.dp,
+ modifier = Modifier.size(48.dp),
+ )
+ }
+ }
+}
+
+private class YieldSupplyActionContentPreviewProvider : PreviewParameterProvider {
+ override val values: Sequence
+ get() = sequenceOf(
+ YieldSupplyActionUM(
+ title = resourceReference(R.string.yield_module_start_earning),
+ subtitle = resourceReference(
+ R.string.yield_module_start_earning_sheet_description,
+ wrappedList("USDT"),
+ ),
+ footer = combinedReference(
+ resourceReference(R.string.yield_module_start_earning_sheet_next_deposits),
+ stringReference(StringsSigns.WHITE_SPACE),
+ resourceReference(R.string.yield_module_start_earning_sheet_fee_policy),
+ ),
+ currencyIconState = CurrencyIconState.Loading,
+ yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
+ transactionDataList = persistentListOf(),
+ feeValue = stringReference("0.00020 ETH • \$0.99"),
+ ),
+ isPrimaryButtonEnabled = false,
+ ),
+ )
+}
+// endregion
\ No newline at end of file
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningComponent.kt
new file mode 100644
index 0000000000..99e5075b5f
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningComponent.kt
@@ -0,0 +1,107 @@
+package com.tangem.features.yield.supply.impl.subcomponents.startearning
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+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.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.core.ui.components.PrimaryButtonIconEnd
+import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
+import com.tangem.core.ui.components.currency.icon.CurrencyIcon
+import com.tangem.core.ui.decompose.ComposableModularContentComponent
+import com.tangem.core.ui.extensions.stringResourceSafe
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.domain.models.currency.CryptoCurrency
+import com.tangem.domain.models.wallet.UserWalletId
+import com.tangem.features.yield.supply.impl.R
+import com.tangem.features.yield.supply.impl.common.ui.YieldSupplyActionContent
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.YieldSupplyStartEarningModel
+
+internal class YieldSupplyStartEarningComponent(
+ private val appComponentContext: AppComponentContext,
+ private val params: Params,
+) : ComposableModularContentComponent, AppComponentContext by appComponentContext {
+
+ private val model: YieldSupplyStartEarningModel = getOrCreateModel(params = params)
+
+ @Composable
+ override fun Title() {
+ TangemModalBottomSheetTitle(
+ endIconRes = R.drawable.ic_close_24,
+ onEndClick = params.callback::onBackClick,
+ )
+ }
+
+ @Suppress("MagicNumber")
+ @Composable
+ override fun Content(modifier: Modifier) {
+ val state by model.uiState.collectAsStateWithLifecycle()
+
+ YieldSupplyActionContent(
+ yieldSupplyActionUM = state,
+ modifier = modifier,
+ ) {
+ Box(
+ modifier = Modifier
+ .width(80.dp)
+ .padding(vertical = 1.dp),
+ ) {
+ CurrencyIcon(
+ state = state.currencyIconState,
+ shouldDisplayNetwork = false,
+ iconSize = 48.dp,
+ modifier = Modifier
+ .size(48.dp)
+ .clip(RoundedCornerShape(48.dp))
+ .align(Alignment.CenterStart),
+ )
+ Image(
+ imageVector = ImageVector.vectorResource(R.drawable.ic_aave_36),
+ contentDescription = null,
+ modifier = Modifier
+ .background(TangemTheme.colors.background.tertiary, RoundedCornerShape(51.dp))
+ .padding(3.dp)
+ .size(48.dp)
+ .align(Alignment.CenterEnd),
+ )
+ }
+ }
+ }
+
+ @Composable
+ override fun Footer() {
+ val state by model.uiState.collectAsStateWithLifecycle()
+
+ PrimaryButtonIconEnd(
+ text = stringResourceSafe(R.string.yield_module_start_earning),
+ onClick = model::onClick,
+ enabled = state.isPrimaryButtonEnabled,
+ iconResId = R.drawable.ic_tangem_24,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ )
+ }
+
+ data class Params(
+ val userWalletId: UserWalletId,
+ val cryptoCurrency: CryptoCurrency,
+ val callback: ModelCallback,
+ )
+
+ interface ModelCallback {
+ fun onBackClick()
+ fun onFeePolicyClick()
+ fun onTransactionSent()
+ }
+}
\ No newline at end of file
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningEntryComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningEntryComponent.kt
new file mode 100644
index 0000000000..d13e9293d2
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningEntryComponent.kt
@@ -0,0 +1,94 @@
+package com.tangem.features.yield.supply.impl.subcomponents.startearning
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import com.arkivanov.decompose.extensions.compose.subscribeAsState
+import com.arkivanov.decompose.router.stack.StackNavigation
+import com.arkivanov.decompose.router.stack.childStack
+import com.arkivanov.decompose.router.stack.pop
+import com.tangem.core.decompose.context.AppComponentContext
+import com.tangem.core.decompose.context.childByContext
+import com.tangem.core.decompose.model.getOrCreateModel
+import com.tangem.core.decompose.navigation.inner.InnerRouter
+import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
+import com.tangem.core.ui.decompose.ComposableModularContentComponent
+import com.tangem.core.ui.decompose.getEmptyComposableModularContentComponent
+import com.tangem.domain.models.currency.CryptoCurrency
+import com.tangem.domain.models.wallet.UserWalletId
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.YieldSupplyStartEarningEntryModel
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.ui.YieldSupplyStartEarningBottomSheet
+
+internal class YieldSupplyStartEarningEntryComponent(
+ private val appComponentContext: AppComponentContext,
+ private val params: Params,
+) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
+
+ private val stackNavigation = StackNavigation()
+
+ private val innerRouter = InnerRouter(
+ stackNavigation = stackNavigation,
+ popCallback = { onChildBack() },
+ )
+
+ private val model: YieldSupplyStartEarningEntryModel = getOrCreateModel(params = params, router = innerRouter)
+
+ private val innerStack = childStack(
+ key = "startEarningStack",
+ source = stackNavigation,
+ serializer = null,
+ initialConfiguration = YieldSupplyStartEarningRoute.Action,
+ handleBackButton = true,
+ childFactory = { configuration, factoryContext ->
+ createChild(
+ configuration,
+ childByContext(
+ componentContext = factoryContext,
+ router = innerRouter,
+ ),
+ )
+ },
+ )
+
+ override fun dismiss() {
+ params.onDismiss(false)
+ }
+
+ @Composable
+ override fun BottomSheet() {
+ val stackState by innerStack.subscribeAsState()
+
+ YieldSupplyStartEarningBottomSheet(
+ stackState = stackState,
+ onDismiss = ::dismiss,
+ )
+ }
+
+ private fun createChild(
+ route: YieldSupplyStartEarningRoute,
+ factoryContext: AppComponentContext,
+ ): ComposableModularContentComponent = when (route) {
+ YieldSupplyStartEarningRoute.FeePolicy -> getEmptyComposableModularContentComponent() // todo fee policy
+ YieldSupplyStartEarningRoute.Action -> YieldSupplyStartEarningComponent(
+ appComponentContext = factoryContext,
+ params = YieldSupplyStartEarningComponent.Params(
+ userWalletId = params.userWalletId,
+ cryptoCurrency = params.cryptoCurrency,
+ callback = model,
+ ),
+ )
+ }
+
+ private fun onChildBack() {
+ if (innerStack.value.backStack.isEmpty()) {
+ dismiss()
+ } else {
+ stackNavigation.pop()
+ }
+ }
+
+ data class Params(
+ val userWalletId: UserWalletId,
+ val cryptoCurrency: CryptoCurrency,
+ val onDismiss: (Boolean) -> Unit,
+ )
+}
\ No newline at end of file
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningRoute.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningRoute.kt
new file mode 100644
index 0000000000..9a8c832586
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningRoute.kt
@@ -0,0 +1,8 @@
+package com.tangem.features.yield.supply.impl.subcomponents.startearning
+
+import com.tangem.core.decompose.navigation.Route
+
+internal sealed class YieldSupplyStartEarningRoute : Route {
+ data object Action : YieldSupplyStartEarningRoute()
+ data object FeePolicy : YieldSupplyStartEarningRoute()
+}
\ No newline at end of file
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/di/YieldSupplyStartEarningModule.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/di/YieldSupplyStartEarningModule.kt
new file mode 100644
index 0000000000..a26ae6c3a0
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/di/YieldSupplyStartEarningModule.kt
@@ -0,0 +1,26 @@
+package com.tangem.features.yield.supply.impl.subcomponents.startearning.di
+
+import com.tangem.core.decompose.di.ModelComponent
+import com.tangem.core.decompose.model.Model
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.YieldSupplyStartEarningEntryModel
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.YieldSupplyStartEarningModel
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+@InstallIn(ModelComponent::class)
+internal interface YieldSupplyStartEarningModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(YieldSupplyStartEarningModel::class)
+ fun provideYieldSupplyStartEarningModel(impl: YieldSupplyStartEarningModel): Model
+
+ @Binds
+ @IntoMap
+ @ClassKey(YieldSupplyStartEarningEntryModel::class)
+ fun provideYieldSupplyStartEarningEntryModel(impl: YieldSupplyStartEarningEntryModel): Model
+}
\ No newline at end of file
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/entity/YieldSupplyStartEarningContentUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/entity/YieldSupplyStartEarningContentUM.kt
new file mode 100644
index 0000000000..0354f3e6e1
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/entity/YieldSupplyStartEarningContentUM.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.yield.supply.impl.subcomponents.startearning.entity
+
+import androidx.compose.runtime.Immutable
+import com.tangem.blockchain.common.transaction.Fee
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
+import com.tangem.core.ui.components.currency.icon.CurrencyIconState
+import com.tangem.core.ui.extensions.TextReference
+
+@Immutable
+internal sealed class YieldSupplyStartEarningContentUM : TangemBottomSheetConfigContent {
+
+ data class Main(
+ val currencyIconState: CurrencyIconState,
+ val fee: Fee?,
+ ) : YieldSupplyStartEarningContentUM()
+
+ data class FeePolicy(
+ val title: TextReference,
+ ) : YieldSupplyStartEarningContentUM()
+}
\ No newline at end of file
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningEntryModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningEntryModel.kt
new file mode 100644
index 0000000000..c48e31cac8
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningEntryModel.kt
@@ -0,0 +1,33 @@
+package com.tangem.features.yield.supply.impl.subcomponents.startearning.model
+
+import com.tangem.core.decompose.di.ModelScoped
+import com.tangem.core.decompose.model.Model
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.decompose.navigation.Router
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningComponent
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningEntryComponent
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningRoute
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import javax.inject.Inject
+
+@ModelScoped
+internal class YieldSupplyStartEarningEntryModel @Inject constructor(
+ override val dispatchers: CoroutineDispatcherProvider,
+ private val router: Router,
+ paramsContainer: ParamsContainer,
+) : Model(), YieldSupplyStartEarningComponent.ModelCallback {
+
+ private val params = paramsContainer.require()
+
+ override fun onBackClick() {
+ router.pop()
+ }
+
+ override fun onFeePolicyClick() {
+ router.push(YieldSupplyStartEarningRoute.FeePolicy)
+ }
+
+ override fun onTransactionSent() {
+ params.onDismiss(true)
+ }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000000..dd5fee8a5e
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt
@@ -0,0 +1,222 @@
+package com.tangem.features.yield.supply.impl.subcomponents.startearning.model
+
+import arrow.core.getOrElse
+import com.tangem.blockchain.common.TransactionSender
+import com.tangem.core.decompose.di.ModelScoped
+import com.tangem.core.decompose.model.Model
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
+import com.tangem.core.ui.extensions.combinedReference
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.core.ui.extensions.wrappedList
+import com.tangem.core.ui.format.bigdecimal.crypto
+import com.tangem.core.ui.format.bigdecimal.fiat
+import com.tangem.core.ui.format.bigdecimal.format
+import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
+import com.tangem.domain.appcurrency.model.AppCurrency
+import com.tangem.domain.models.currency.CryptoCurrencyStatus
+import com.tangem.domain.models.wallet.UserWallet
+import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
+import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
+import com.tangem.domain.transaction.usecase.SendTransactionUseCase
+import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
+import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase
+import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase
+import com.tangem.features.yield.supply.impl.R
+import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
+import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningComponent
+import com.tangem.utils.StringsSigns.DOT
+import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.collections.immutable.toPersistentList
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
+import timber.log.Timber
+import java.math.BigDecimal
+import javax.inject.Inject
+import kotlin.properties.Delegates
+
+@Suppress("LongParameterList")
+@ModelScoped
+internal class YieldSupplyStartEarningModel @Inject constructor(
+ override val dispatchers: CoroutineDispatcherProvider,
+ paramsContainer: ParamsContainer,
+ private val getUserWalletUseCase: GetUserWalletUseCase,
+ private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
+ private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
+ private val sendTransactionUseCase: SendTransactionUseCase,
+ private val yieldSupplyStartEarningUseCase: YieldSupplyStartEarningUseCase,
+ private val yieldSupplyEstimateEnterFeeUseCase: YieldSupplyEstimateEnterFeeUseCase,
+ private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
+) : Model() {
+
+ private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
+
+ private val cryptoCurrency = params.cryptoCurrency
+ private var userWallet: UserWallet by Delegates.notNull()
+
+ private val cryptoCurrencyStatusFlow: StateFlow
+ field = MutableStateFlow(
+ CryptoCurrencyStatus(
+ currency = cryptoCurrency,
+ value = CryptoCurrencyStatus.Loading,
+ ),
+ )
+ private val feeCryptoCurrencyStatusFlow: StateFlow
+ field = MutableStateFlow(
+ CryptoCurrencyStatus(
+ currency = cryptoCurrency,
+ value = CryptoCurrencyStatus.Loading,
+ ),
+ )
+
+ val uiState: StateFlow
+ field: MutableStateFlow = MutableStateFlow(
+ YieldSupplyActionUM(
+ title = resourceReference(R.string.yield_module_start_earning),
+ subtitle = resourceReference(
+ R.string.yield_module_start_earning_sheet_description,
+ wrappedList(cryptoCurrency.symbol),
+ ),
+ footer = combinedReference(
+ resourceReference(R.string.yield_module_start_earning_sheet_next_deposits),
+ resourceReference(R.string.yield_module_start_earning_sheet_fee_policy),
+ ),
+ currencyIconState = CryptoCurrencyToIconStateConverter().convert(params.cryptoCurrency),
+ yieldSupplyFeeUM = YieldSupplyFeeUM.Loading,
+ isPrimaryButtonEnabled = false,
+ ),
+ )
+
+ private val cryptoCurrencyStatus
+ get() = cryptoCurrencyStatusFlow.value
+ private var appCurrency = AppCurrency.Default
+
+ init {
+ modelScope.launch {
+ appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
+ subscribeOnCurrencyStatusUpdates()
+ }
+ }
+
+ private suspend fun onLoadFee() {
+ if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) return
+
+ val transactionListData = yieldSupplyStartEarningUseCase(
+ userWalletId = userWallet.walletId,
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ ).getOrNull() ?: return
+
+ val updatedTransactionList = yieldSupplyEstimateEnterFeeUseCase.invoke(
+ userWallet = userWallet,
+ cryptoCurrency = cryptoCurrency,
+ transactionDataList = transactionListData,
+ ).getOrNull() ?: return
+
+ val feeSum = updatedTransactionList.sumOf {
+ it.fee?.amount?.value ?: BigDecimal.ZERO
+ }
+
+ val crypto = feeSum.format { crypto(feeCryptoCurrencyStatusFlow.value.currency) }
+ val fiatFeeValue = cryptoCurrencyStatus.value.fiatRate?.let { rate ->
+ feeSum.multiply(rate)
+ }
+
+ val fiat = fiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
+
+ uiState.update {
+ if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
+ it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
+ } else {
+ it.copy(
+ isPrimaryButtonEnabled = true, // todo yield supply check for notifications
+ yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
+ transactionDataList = updatedTransactionList.toPersistentList(),
+ feeValue = combinedReference(
+ stringReference(crypto),
+ stringReference(" $DOT "),
+ stringReference(fiat),
+ ),
+ ),
+ )
+ }
+ }
+ }
+
+ fun onClick() {
+ val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return
+
+ uiState.update { it.copy(isPrimaryButtonEnabled = false) }
+ modelScope.launch(dispatchers.default) {
+ sendTransactionUseCase.invoke(
+ txsData = yieldSupplyFeeUM.transactionDataList,
+ userWallet = userWallet,
+ network = cryptoCurrency.network,
+ sendMode = TransactionSender.MultipleTransactionSendMode.DEFAULT,
+ ).fold(
+ ifLeft = {
+ Timber.e(it.toString())
+ uiState.update { it.copy(isPrimaryButtonEnabled = true) }
+ },
+ ifRight = {
+ params.callback.onTransactionSent()
+ },
+ )
+ }
+ }
+
+ private fun subscribeOnCurrencyStatusUpdates() {
+ modelScope.launch {
+ getUserWalletUseCase(params.userWalletId).fold(
+ ifRight = { wallet ->
+ userWallet = wallet
+ getCurrenciesStatusUpdates()
+ },
+ ifLeft = {
+ Timber.w(it.toString())
+ // showAlertError() todo yield supply error alert
+ return@launch
+ },
+ )
+ }
+ }
+
+ private fun getCurrenciesStatusUpdates() {
+ getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
+ userWalletId = params.userWalletId,
+ currencyId = cryptoCurrency.id,
+ isSingleWalletWithTokens = false,
+ ).onEach { maybeCryptoCurrency ->
+ maybeCryptoCurrency.fold(
+ ifRight = { cryptoCurrencyStatus ->
+ onDataLoaded(
+ currencyStatus = cryptoCurrencyStatus,
+ feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(
+ userWalletId = params.userWalletId,
+ cryptoCurrencyStatus = cryptoCurrencyStatus,
+ ).getOrNull() ?: cryptoCurrencyStatus,
+ )
+ },
+ ifLeft = {
+ // todo yield supply error
+ // sendConfirmAlertFactory.getGenericErrorState(
+ // onFailedTxEmailClick = {
+ // onFailedTxEmailClick(it.toString())
+ // },
+ // popBack = router::pop,
+ // )
+ },
+ )
+ }.launchIn(modelScope)
+ }
+
+ private fun onDataLoaded(currencyStatus: CryptoCurrencyStatus, feeCurrencyStatus: CryptoCurrencyStatus) {
+ cryptoCurrencyStatusFlow.update { currencyStatus }
+ feeCryptoCurrencyStatusFlow.update { feeCurrencyStatus }
+
+ modelScope.launch {
+ onLoadFee()
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/ui/YieldSupplyStartEarningBottomSheet.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/ui/YieldSupplyStartEarningBottomSheet.kt
new file mode 100644
index 0000000000..aba821a580
--- /dev/null
+++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/ui/YieldSupplyStartEarningBottomSheet.kt
@@ -0,0 +1,48 @@
+package com.tangem.features.yield.supply.impl.subcomponents.startearning.ui
+
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import com.arkivanov.decompose.router.stack.ChildStack
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
+import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter
+import com.tangem.core.ui.decompose.ComposableModularContentComponent
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.features.yield.supply.impl.subcomponents.startearning.YieldSupplyStartEarningRoute
+
+@Composable
+internal fun YieldSupplyStartEarningBottomSheet(
+ stackState: ChildStack,
+ onDismiss: () -> Unit,
+) {
+ TangemModalBottomSheetWithFooter(
+ config = TangemBottomSheetConfig(
+ isShown = true,
+ onDismissRequest = onDismiss,
+ content = TangemBottomSheetConfigContent.Empty,
+ ),
+ containerColor = TangemTheme.colors.background.tertiary,
+ title = {
+ AnimatedContent(
+ stackState.active.instance,
+ ) { currentState ->
+ currentState.Title()
+ }
+ },
+ footer = {
+ AnimatedContent(
+ stackState.active.instance,
+ ) { currentState ->
+ currentState.Footer()
+ }
+ },
+ content = {
+ AnimatedContent(
+ stackState.active.instance,
+ ) { currentState ->
+ currentState.Content(modifier = Modifier)
+ }
+ },
+ )
+}
\ No newline at end of file
diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml
index 009240b10c..5f6869584a 100644
--- a/gradle/tangem_dependencies.toml
+++ b/gradle/tangem_dependencies.toml
@@ -5,7 +5,8 @@
# https://github.com/tangem/tangem-sdk-android/
# https://github.com/tangem/vico
-tangemBlockchainSdk = "develop-1229"
+tangemBlockchainSdk = "develop-1230"
+#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-561"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
tangemVico = "2.0.0-alpha.25-tangem12"