diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt index 1b342eb608..a047e3deae 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt @@ -4,6 +4,7 @@ import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.error.FeeErrorResolver import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetContractAddressUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase import dagger.Module @@ -47,4 +48,15 @@ internal object YieldSupplyDomainModule { feeErrorResolver = feeErrorResolver, ) } + + @Provides + @Singleton + fun provideYieldSupplyGetContractAddressUseCase( + yieldSupplyTransactionRepository: YieldSupplyTransactionRepository, + ): YieldSupplyGetContractAddressUseCase { + return YieldSupplyGetContractAddressUseCase( + yieldSupplyTransactionRepository = yieldSupplyTransactionRepository, + + ) + } } \ No newline at end of file diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt index d804ebb1c3..7028b8e86c 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt @@ -138,7 +138,7 @@ internal class DefaultYieldSupplyTransactionRepository( else -> Unit } - if (yieldTokenStatus?.isAllowedToSpend == false) { + if (yieldTokenStatus?.isAllowedToSpend != true) { enterTransactions.add( createTransaction( walletManager = walletManager, @@ -182,7 +182,7 @@ internal class DefaultYieldSupplyTransactionRepository( .getOrNull() } - private suspend fun getYieldContractAddress(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String? = + override suspend fun getYieldContractAddress(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String? = withContext(dispatchers.io) { require(cryptoCurrency is CryptoCurrency.Token) runCatching { @@ -203,13 +203,18 @@ internal class DefaultYieldSupplyTransactionRepository( require(cryptoCurrency is CryptoCurrency.Token) runCatching { val sdkSupplyStatus = walletManager.getYieldSupplyStatus(cryptoCurrency.contractAddress) - val isAllowedToSpend = walletManager.isAllowedToSpend(cryptoCurrency.contractAddress) + val isAllowedToSpend = walletManager.isAllowedToSpend( + Token( + symbol = cryptoCurrency.symbol, + contractAddress = cryptoCurrency.contractAddress, + decimals = cryptoCurrency.decimals, + ), + ) YieldSupplyStatus( isActive = sdkSupplyStatus?.isActive == true, isInitialized = sdkSupplyStatus?.isInitialized == true, isAllowedToSpend = isAllowedToSpend, - // maxNetworkFee = sdkSupplyStatus?.maxNetworkFee, ) }.onFailure(Timber::e).getOrNull() } diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyTransactionRepository.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyTransactionRepository.kt index 17b679579a..4804e82246 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyTransactionRepository.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyTransactionRepository.kt @@ -22,4 +22,6 @@ interface YieldSupplyTransactionRepository { yieldSupplyStatus: YieldSupplyStatus, fee: Fee?, ): TransactionData.Uncompiled + + suspend fun getYieldContractAddress(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String? } \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetContractAddressUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetContractAddressUseCase.kt new file mode 100644 index 0000000000..4526550e39 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetContractAddressUseCase.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.yield.supply.usecase + +import arrow.core.Either +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository + +class YieldSupplyGetContractAddressUseCase( + private val yieldSupplyTransactionRepository: YieldSupplyTransactionRepository, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency.Token, + ): Either = Either.catch { + yieldSupplyTransactionRepository.getYieldContractAddress( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrency, + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index e0f5949b5a..a9f3f1beb1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -141,7 +141,8 @@ sealed class WalletNotification(val config: NotificationConfig) { data object YeildSupplyApprove : Warning( title = resourceReference(R.string.yield_module_main_view_approve_notification_title), - subtitle = resourceReference(R.string.yield_module_main_view_approve_notification_description), + // todo yield supply lokalise + subtitle = resourceReference(R.string.yield_module_main_view_approve_notification_title), ) } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/YieldSupplyActiveComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/YieldSupplyActiveComponent.kt index 2b3bd18f57..45f5276117 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/YieldSupplyActiveComponent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/YieldSupplyActiveComponent.kt @@ -59,5 +59,6 @@ internal class YieldSupplyActiveComponent( interface ModelCallback { fun onBackClick() fun onStopEarning() + fun onApprove() } } \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/YieldSupplyActiveEntryComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/YieldSupplyActiveEntryComponent.kt index 258da13fe2..4bca6d3382 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/YieldSupplyActiveEntryComponent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/YieldSupplyActiveEntryComponent.kt @@ -17,6 +17,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.features.yield.supply.impl.subcomponents.active.model.YieldSupplyActiveEntryModel import com.tangem.features.yield.supply.impl.subcomponents.active.model.YieldSupplyActiveRoute import com.tangem.features.yield.supply.impl.subcomponents.active.ui.YieldSupplyActiveEntryBottomSheet +import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent import kotlinx.coroutines.flow.StateFlow @@ -77,7 +78,7 @@ internal class YieldSupplyActiveEntryComponent( callback = model, ), ) - YieldSupplyActiveRoute.Action -> YieldSupplyStopEarningComponent( + YieldSupplyActiveRoute.Exit -> YieldSupplyStopEarningComponent( appComponentContext = factoryContext, params = YieldSupplyStopEarningComponent.Params( userWallet = params.userWallet, @@ -85,6 +86,14 @@ internal class YieldSupplyActiveEntryComponent( callback = model, ), ) + YieldSupplyActiveRoute.Approve -> YieldSupplyApproveComponent( + appComponentContext = factoryContext, + params = YieldSupplyApproveComponent.Params( + userWallet = params.userWallet, + cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow, + callback = model, + ), + ) } private fun onChildBack() { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/entity/YieldSupplyActiveContentUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/entity/YieldSupplyActiveContentUM.kt index c95c069d2d..222df4dcd8 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/entity/YieldSupplyActiveContentUM.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/entity/YieldSupplyActiveContentUM.kt @@ -1,5 +1,6 @@ package com.tangem.features.yield.supply.impl.subcomponents.active.entity +import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.extensions.TextReference internal data class YieldSupplyActiveContentUM( @@ -8,4 +9,5 @@ internal data class YieldSupplyActiveContentUM( val providerTitle: TextReference, val subtitle: TextReference, val subtitleLink: TextReference, + val notificationUM: NotificationUM?, ) \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveEntryModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveEntryModel.kt index 57878b486a..1d60926512 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveEntryModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveEntryModel.kt @@ -6,6 +6,7 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveComponent import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveEntryComponent +import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent import com.tangem.utils.coroutines.CoroutineDispatcherProvider import javax.inject.Inject @@ -15,12 +16,14 @@ internal class YieldSupplyActiveEntryModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, paramsContainer: ParamsContainer, private val router: Router, -) : Model(), YieldSupplyActiveComponent.ModelCallback, YieldSupplyStopEarningComponent.ModelCallback { +) : Model(), YieldSupplyActiveComponent.ModelCallback, + YieldSupplyStopEarningComponent.ModelCallback, + YieldSupplyApproveComponent.ModelCallback { private val params = paramsContainer.require() override fun onStopEarning() { - router.push(YieldSupplyActiveRoute.Action) + router.push(YieldSupplyActiveRoute.Exit) } override fun onBackClick() { @@ -30,4 +33,8 @@ internal class YieldSupplyActiveEntryModel @Inject constructor( override fun onTransactionSent() { params.onDismiss() } + + override fun onApprove() { + router.push(YieldSupplyActiveRoute.Approve) + } } \ No newline at end of file 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 f0d76c7bca..de5b72b33d 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 @@ -1,8 +1,10 @@ package com.tangem.features.yield.supply.impl.subcomponents.active.model +import com.tangem.common.ui.notifications.NotificationUM 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.notifications.NotificationConfig import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList @@ -12,8 +14,7 @@ import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveComponent import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.* import javax.inject.Inject @ModelScoped @@ -42,6 +43,32 @@ internal class YieldSupplyActiveModel @Inject constructor( formatArgs = wrappedList(cryptoCurrency.symbol, cryptoCurrency.symbol), ), subtitleLink = resourceReference(R.string.common_read_more), + notificationUM = null, ), ) + + init { + subscribeOnCurrencyUpdates() + } + + private fun subscribeOnCurrencyUpdates() { + cryptoCurrencyStatusFlow.onEach { cryptoCurrencyStatus -> + val approvalNotification = if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isAllowedToSpend != true) { + NotificationUM.Error( + title = resourceReference(R.string.yield_module_approve_needed_notification_title), + subtitle = resourceReference(R.string.yield_module_approve_needed_notification_description), + iconResId = R.drawable.ic_alert_triangle_20, + buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig( + text = resourceReference(R.string.yield_module_approve_needed_notification_cta), + onClick = params.callback::onApprove, + ), + ) + } else { + null + } + + uiState.update { it.copy(notificationUM = approvalNotification) } + }.flowOn(dispatchers.default) + .launchIn(modelScope) + } } \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveRoute.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveRoute.kt index a143cc777f..800a76040e 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveRoute.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveRoute.kt @@ -4,5 +4,6 @@ import com.tangem.core.decompose.navigation.Route internal sealed class YieldSupplyActiveRoute : Route { data object Info : YieldSupplyActiveRoute() - data object Action : YieldSupplyActiveRoute() + data object Exit : YieldSupplyActiveRoute() + data object Approve : YieldSupplyActiveRoute() } \ No newline at end of file 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 394a786540..3f757c7f1b 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 @@ -1,6 +1,7 @@ package com.tangem.features.yield.supply.impl.subcomponents.active.ui import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -8,6 +9,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -19,10 +21,12 @@ 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.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.ResizableText 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.notifications.Notification import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -58,6 +62,15 @@ internal fun YieldSupplyActiveContent(state: YieldSupplyActiveContentUM, modifie ) } YieldSupplyActiveMyFunds(state = state) + + AnimatedVisibility(state.notificationUM != null) { + val wrappedNotification = remember(this) { requireNotNull(state.notificationUM) } + Notification( + config = wrappedNotification.config, + iconTint = null, + containerColor = TangemTheme.colors.background.action, + ) + } } } @@ -182,6 +195,7 @@ private class YieldSupplyActiveBottomSheetPreviewProvider : PreviewParameterProv wrappedList("USDT", "USDT"), ), subtitleLink = resourceReference(R.string.common_read_more), + notificationUM = NotificationUM.Error.InvalidAmount, ), ) } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/YieldSupplyApproveComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/YieldSupplyApproveComponent.kt new file mode 100644 index 0000000000..68cfde8d7f --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/YieldSupplyApproveComponent.kt @@ -0,0 +1,110 @@ +package com.tangem.features.yield.supply.impl.subcomponents.approve + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +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.context.child +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.decompose.ComposableModularContentComponent +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWallet +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.approve.model.YieldSupplyApproveModel +import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent +import kotlinx.coroutines.flow.StateFlow + +@Suppress("MagicNumber") +internal class YieldSupplyApproveComponent( + private val appComponentContext: AppComponentContext, + private val params: Params, +) : ComposableModularContentComponent, AppComponentContext by appComponentContext { + + private val model: YieldSupplyApproveModel = getOrCreateModel(params = params) + + private val yieldSupplyNotificationsComponent = YieldSupplyNotificationsComponent( + appComponentContext = child("yieldSupplyApproveNotifications"), + params = YieldSupplyNotificationsComponent.Params( + userWalletId = params.userWallet.walletId, + cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow, + feeCryptoCurrencyStatusFlow = model.feeCryptoCurrencyStatusFlow, + callback = model, + ), + ) + + @Composable + override fun Title() { + TangemModalBottomSheetTitle( + startIconRes = R.drawable.ic_back_24, + onStartClick = params.callback::onBackClick, + ) + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + YieldSupplyActionContent( + yieldSupplyActionUM = state, + onFooterClick = model::onReadMoreClick, + yieldSupplyNotificationsComponent = yieldSupplyNotificationsComponent, + modifier = modifier, + ) { + Box( + modifier = Modifier + .size(56.dp) + .background(TangemTheme.colors.icon.accent.copy(0.1f), CircleShape), + ) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_check_circle_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + modifier = Modifier + .padding(12.dp) + .size(32.dp), + ) + } + } + } + + @Composable + override fun Footer() { + val state by model.uiState.collectAsStateWithLifecycle() + + PrimaryButtonIconEnd( + text = stringResourceSafe(R.string.common_confirm), + onClick = model::onClick, + iconResId = R.drawable.ic_tangem_24, + enabled = state.isPrimaryButtonEnabled, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + } + + data class Params( + val userWallet: UserWallet, + val cryptoCurrencyStatusFlow: StateFlow, + val callback: ModelCallback, + ) + + interface ModelCallback { + fun onBackClick() + 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/approve/di/YieldSupplyApproveModule.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/di/YieldSupplyApproveModule.kt new file mode 100644 index 0000000000..ea089f5dba --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/di/YieldSupplyApproveModule.kt @@ -0,0 +1,20 @@ +package com.tangem.features.yield.supply.impl.subcomponents.approve.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.yield.supply.impl.subcomponents.approve.model.YieldSupplyApproveModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(ModelComponent::class) +internal interface YieldSupplyApproveModule { + + @Binds + @IntoMap + @ClassKey(YieldSupplyApproveModel::class) + fun provideYieldSupplyApproveModel(impl: YieldSupplyApproveModel): Model +} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt new file mode 100644 index 0000000000..a8914d3117 --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt @@ -0,0 +1,225 @@ +package com.tangem.features.yield.supply.impl.subcomponents.approve.model + +import arrow.core.getOrElse +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.navigation.url.UrlOpener +import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +import com.tangem.core.ui.extensions.* +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.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase +import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase +import com.tangem.domain.transaction.usecase.GetFeeUseCase +import com.tangem.domain.transaction.usecase.SendTransactionUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetContractAddressUseCase +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.approve.YieldSupplyApproveComponent +import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent +import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger +import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData +import com.tangem.utils.StringsSigns.DOT +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.persistentListOf +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +@ModelScoped +internal class YieldSupplyApproveModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, + private val urlOpener: UrlOpener, + private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger, + private val createApprovalTransactionUseCase: CreateApprovalTransactionUseCase, + private val getFeeUseCase: GetFeeUseCase, + private val sendTransactionUseCase: SendTransactionUseCase, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, + private val yieldSupplyGetContractAddressUseCase: YieldSupplyGetContractAddressUseCase, +) : Model(), YieldSupplyNotificationsComponent.ModelCallback { + + private val params: YieldSupplyApproveComponent.Params = paramsContainer.require() + + private val cryptoCurrencyStatus + get() = params.cryptoCurrencyStatusFlow.value + private val cryptoCurrency = cryptoCurrencyStatus.currency + private var userWallet = params.userWallet + + val feeCryptoCurrencyStatusFlow: StateFlow + field = MutableStateFlow( + CryptoCurrencyStatus( + cryptoCurrencyStatus.currency, + value = CryptoCurrencyStatus.Loading, + ), + ) + + private val feeCryptoCurrencyStatus: CryptoCurrencyStatus + get() = feeCryptoCurrencyStatusFlow.value + + private var appCurrency = AppCurrency.Default + + val uiState: StateFlow + field: MutableStateFlow = MutableStateFlow( + YieldSupplyActionUM( + title = resourceReference(R.string.yield_module_approve_sheet_title), + subtitle = resourceReference( + id = R.string.yield_module_approve_sheet_subtitle, + formatArgs = wrappedList(cryptoCurrency.symbol), + ), + footer = resourceReference(R.string.yield_module_approve_sheet_fee_note), + footerLink = resourceReference(R.string.common_read_more), + currencyIconState = CryptoCurrencyToIconStateConverter().convert(cryptoCurrency), + yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, + isPrimaryButtonEnabled = false, + ), + ) + + init { + modelScope.launch { + appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } + subscribeOnCurrencyStatusUpdates() + subscribeOnNotificationsErrors() + } + } + + fun onReadMoreClick() { + urlOpener.openUrl(READ_MORE_URL) + } + + override fun onFeeReload() { + modelScope.launch { + onLoadFee() + } + } + + fun onClick() { + val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return + uiState.update { it.copy(isPrimaryButtonEnabled = false) } + + modelScope.launch(dispatchers.default) { + sendTransactionUseCase( + txData = yieldSupplyFeeUM.transactionDataList.first(), + userWallet = userWallet, + network = cryptoCurrency.network, + ).fold( + ifLeft = { + Timber.e(it.toString()) + }, + ifRight = { + params.callback.onTransactionSent() + }, + ) + } + } + + private fun subscribeOnCurrencyStatusUpdates() { + modelScope.launch { + feeCryptoCurrencyStatusFlow.update { + getFeePaidCryptoCurrencyStatusSyncUseCase( + userWalletId = userWallet.walletId, + cryptoCurrencyStatus = cryptoCurrencyStatus, + ).getOrNull() ?: cryptoCurrencyStatus + } + onLoadFee() + } + } + + private suspend fun onLoadFee() { + if (cryptoCurrency !is CryptoCurrency.Token) return + + val contractAddress = yieldSupplyGetContractAddressUseCase.invoke( + userWalletId = userWallet.walletId, + cryptoCurrency = cryptoCurrency, + ).getOrNull() ?: return + + val approvalTransitionData = createApprovalTransactionUseCase( + cryptoCurrency = cryptoCurrency, + userWalletId = userWallet.walletId, + contractAddress = cryptoCurrency.contractAddress, + spenderAddress = contractAddress, + amount = null, + ).getOrElse { + Timber.e(it) + return + } + + uiState.update { it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading) } + + getFeeUseCase( + transactionData = approvalTransitionData, + userWallet = userWallet, + network = cryptoCurrency.network, + ).fold( + ifLeft = { feeError -> + yieldSupplyNotificationsUpdateTrigger.triggerUpdate( + data = YieldSupplyNotificationData( + feeValue = null, + feeError = feeError, + ), + ) + uiState.update { + it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Error) + } + }, + ifRight = { fee -> + val feeCryptoValue = fee.normal.amount.value + + val feeFiatValue = feeCryptoCurrencyStatus.value.fiatRate?.let { rate -> + feeCryptoValue?.multiply(rate) + } + val cryptoFee = feeCryptoValue.format { crypto(feeCryptoCurrencyStatus.currency) } + val fiatFee = feeFiatValue.format { fiat(appCurrency.code, appCurrency.symbol) } + + uiState.update { + if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) { + it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading) + } else { + it.copy( + isPrimaryButtonEnabled = true, + yieldSupplyFeeUM = YieldSupplyFeeUM.Content( + transactionDataList = persistentListOf(approvalTransitionData.copy(fee = fee.normal)), + feeValue = combinedReference( + stringReference(cryptoFee), + stringReference(" $DOT "), + stringReference(fiatFee), + ), + currentNetworkFeeValue = TextReference.EMPTY, + maxNetworkFeeValue = TextReference.EMPTY, + ), + ) + } + } + yieldSupplyNotificationsUpdateTrigger.triggerUpdate( + data = YieldSupplyNotificationData( + feeValue = feeCryptoValue, + feeError = null, + ), + ) + }, + ) + } + + private fun subscribeOnNotificationsErrors() { + yieldSupplyNotificationsUpdateTrigger.hasErrorFlow + .onEach { hasError -> + uiState.update { + it.copy(isPrimaryButtonEnabled = !hasError) + } + }.launchIn(modelScope) + } + + private companion object { + const val READ_MORE_URL = "https://tangem.com" // todo yield supply replace with real link + } +} \ No newline at end of file diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 958e54058c..fa358b8aed 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-1230" +tangemBlockchainSdk = "develop-1236" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-564" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^