diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 41205cfc6e..ba9756ce59 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -9,7 +9,6 @@ import com.tangem.feature.stories.api.StoriesComponent import com.tangem.feature.usedesk.api.UsedeskComponent import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.features.account.AccountCreateEditComponent -import com.tangem.features.commonfeatures.api.addfunds.AddFundsComponent import com.tangem.features.account.AccountDetailsComponent import com.tangem.features.account.ArchivedAccountListComponent import com.tangem.features.createwalletselection.CreateWalletSelectionComponent @@ -116,7 +115,6 @@ internal class ChildFactory @Inject constructor( private val surveyComponentFactory: SurveyComponent.Factory, private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory, private val feedEntryComponentFactory: FeedEntryComponent.Factory, - private val addFundsComponentFactory: AddFundsComponent.Factory, ) { @Suppress("LongMethod", "CyclomaticComplexMethod") @@ -237,13 +235,6 @@ internal class ChildFactory @Inject constructor( componentFactory = buyCryptoComponentFactory, ) } - is AppRoute.AddFunds -> { - createComponentChild( - context = context, - params = AddFundsComponent.Params(userWalletId = route.userWalletId), - componentFactory = addFundsComponentFactory, - ) - } is AppRoute.SellCrypto -> { createComponentChild( context = context, diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 02d03d772d..b5e2aac206 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -59,11 +59,6 @@ sealed class AppRoute(val path: String) : Route { @Serializable data object Wallet : AppRoute(path = "/wallet") - @Serializable - data class AddFunds( - val userWalletId: UserWalletId, - ) : AppRoute(path = "/add_funds/${userWalletId.stringValue}") - @Serializable data class CurrencyDetails( val userWalletId: UserWalletId, diff --git a/common/ui-markets/src/main/kotlin/com/tangem/common/ui/markets/action/TokenActionsHandler.kt b/common/ui-markets/src/main/kotlin/com/tangem/common/ui/markets/action/TokenActionsHandler.kt index e24e2ee1ed..4685c359d0 100644 --- a/common/ui-markets/src/main/kotlin/com/tangem/common/ui/markets/action/TokenActionsHandler.kt +++ b/common/ui-markets/src/main/kotlin/com/tangem/common/ui/markets/action/TokenActionsHandler.kt @@ -34,7 +34,7 @@ class TokenActionsHandler @AssistedInject constructor( private val urlOpener: UrlOpener, private val analyticsEventHandler: AnalyticsEventHandler, @Assisted private val currentAppCurrency: Provider, - @Assisted private val onHandleQuickAction: (HandledQuickAction) -> Unit, + @Assisted private val onHandleQuickAction: (action: HandledQuickAction, shouldDismiss: Boolean) -> Unit, private val isDemoCardUseCase: IsDemoCardUseCase, private val messageSender: UiMessageSender, ) { @@ -49,6 +49,18 @@ class TokenActionsHandler @AssistedInject constructor( action = action, cryptoCurrencyData = cryptoCurrencyData, ), + when (action) { + TokenActionsBSContentUM.Action.Receive, + TokenActionsBSContentUM.Action.CopyAddress, + TokenActionsBSContentUM.Action.Sell, + -> false + TokenActionsBSContentUM.Action.Send, + TokenActionsBSContentUM.Action.Stake, + TokenActionsBSContentUM.Action.YieldMode, + TokenActionsBSContentUM.Action.Buy, + TokenActionsBSContentUM.Action.Exchange, + -> true + }, ) val userWallet = cryptoCurrencyData.userWallet if (userWallet is UserWallet.Cold && handleDemoMode(action, userWallet)) return @@ -164,7 +176,7 @@ class TokenActionsHandler @AssistedInject constructor( interface Factory { fun create( currentAppCurrency: Provider, - onHandleQuickAction: (HandledQuickAction) -> Unit, + onHandleQuickAction: (HandledQuickAction, shouldDismiss: Boolean) -> Unit, ): TokenActionsHandler } diff --git a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addfunds/AddFundsComponent.kt b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addfunds/AddFundsComponent.kt index c8481039a9..94533d5fda 100644 --- a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addfunds/AddFundsComponent.kt +++ b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addfunds/AddFundsComponent.kt @@ -1,14 +1,29 @@ package com.tangem.features.commonfeatures.api.addfunds import com.tangem.core.decompose.factory.ComponentFactory -import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId -interface AddFundsComponent : ComposableContentComponent { +interface AddFundsComponent : ComposableBottomSheetComponent { data class Params( - val userWalletId: UserWalletId, + val launchMode: LaunchMode, + val onDismiss: () -> Unit, ) + sealed interface LaunchMode { + data class ChooseToken(val userWalletId: UserWalletId) : LaunchMode + + data class TokenActionsOnly( + val userWalletId: UserWalletId, + val currency: CryptoCurrency, + ) : LaunchMode + + data class FilteredByRawId( + val rawCurrencyId: CryptoCurrency.RawID, + ) : LaunchMode + } + interface Factory : ComponentFactory } \ No newline at end of file diff --git a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addtoportfolio/AddToPortfolioManager.kt b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addtoportfolio/AddToPortfolioManager.kt index b4d0f52a3f..eaa72c6758 100644 --- a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addtoportfolio/AddToPortfolioManager.kt +++ b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addtoportfolio/AddToPortfolioManager.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager.AnalyticsParams import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioFetcher +import com.tangem.features.commonfeatures.api.tokenactions.BottomAction import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.SharedFlow @@ -94,7 +95,14 @@ interface AddToPortfolioManager : AddToPortfolioManagerInternal { val wallet: UserWallet, val account: AccountStatus.CryptoPortfolio, val addedCurrency: CryptoCurrencyStatus, + val meta: FinishMeta = FinishMeta.None, ) + + sealed interface FinishMeta { + data object None : FinishMeta + data object OnQuickAction : FinishMeta + data class OnBottomAction(val action: BottomAction) : FinishMeta + } } /** diff --git a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/choosetoken/ChooseTokenBridge.kt b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/choosetoken/ChooseTokenBridge.kt index cc89e84d25..daf50812c5 100644 --- a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/choosetoken/ChooseTokenBridge.kt +++ b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/choosetoken/ChooseTokenBridge.kt @@ -29,6 +29,7 @@ interface ChooseTokenBridge : ChooseTokenBridgeInternal { val title: TextReference, val isShowMarketBlock: Boolean, val isShowPaymentAccount: Boolean, + val isAppBarShown: Boolean = true, ) { companion object { val SwapFrom = Settings( @@ -45,6 +46,7 @@ interface ChooseTokenBridge : ChooseTokenBridgeInternal { title = resourceReference(R.string.swapping_to_title), isShowMarketBlock = true, isShowPaymentAccount = false, + isAppBarShown = false, ) } } @@ -90,6 +92,11 @@ data class ChooseTokenResult( val analyticsPayload: Set = emptySet(), ) { val walletId get() = wallet.walletId + + val wasJustAdded: Boolean + get() = analyticsPayload + .filterIsInstance() + .any { it.value } } sealed interface ChooseTokenAnalyticsPayload { diff --git a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/tokenactions/BottomAction.kt b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/tokenactions/BottomAction.kt new file mode 100644 index 0000000000..5d6e3c46bc --- /dev/null +++ b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/tokenactions/BottomAction.kt @@ -0,0 +1,3 @@ +package com.tangem.features.commonfeatures.api.tokenactions + +enum class BottomAction { GoToToken, None } \ No newline at end of file diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/DefaultAddFundsComponent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/DefaultAddFundsComponent.kt index 984583d5e7..bd8a8d939e 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/DefaultAddFundsComponent.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/DefaultAddFundsComponent.kt @@ -1,93 +1,232 @@ package com.tangem.features.commonfeatures.impl.addfunds -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect 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.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.bottomsheets.LocalTangemBottomSheetContentBottomInset +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle -import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType +import com.tangem.core.ui.ds.topbar.TangemTopBar +import com.tangem.core.ui.ds.topbar.TangemTopBarType +import com.tangem.core.ui.extensions.clickableSingle import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemeRedesign import com.tangem.features.commonfeatures.api.addfunds.AddFundsComponent import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenComponent import com.tangem.features.commonfeatures.impl.R import com.tangem.features.commonfeatures.impl.addfunds.model.AddFundsModel -import com.tangem.features.commonfeatures.impl.addtoportfolio.TokenActionsComponent +import com.tangem.features.commonfeatures.impl.addfunds.model.uiSpec +import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent +import com.tangem.features.commonfeatures.impl.userportfolio.UserPortfolioComponent +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import com.tangem.core.ui.R as CoreR +@Suppress("LongParameterList") internal class DefaultAddFundsComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @Assisted private val params: AddFundsComponent.Params, chooseTokenComponentFactory: ChooseTokenComponent.Factory, tokenActionsComponentFactory: TokenActionsComponent.Factory, + userPortfolioComponentFactory: UserPortfolioComponent.Factory, + walletFeatureToggles: WalletFeatureToggles, ) : AppComponentContext by appComponentContext, AddFundsComponent { private val model: AddFundsModel = getOrCreateModel(params) - private val chooseTokenComponent: ChooseTokenComponent = chooseTokenComponentFactory.create( - context = child(key = "addFundsChooseToken"), - params = ChooseTokenComponent.Params(bridge = model.chooseTokenBridge), - ) + private val isCompactTokenActions: Boolean = params.launchMode is AddFundsComponent.LaunchMode.TokenActionsOnly - private val tokenActionsComponent: TokenActionsComponent = tokenActionsComponentFactory.create( - context = child(key = "addFundsTokenActions"), - params = TokenActionsComponent.Params( - data = model.tokenActionsData, - callbacks = model, - bottomAction = TokenActionsComponent.BottomAction.GoToToken, - isRedesignForced = true, - ), - ) + private val isAddFundsStage1Enabled: Boolean = walletFeatureToggles.isAddFundsStage1Enabled + + private val tokenActionsComponent: TokenActionsComponent by lazy { + tokenActionsComponentFactory.create( + context = child(key = "addFundsTokenActions"), + params = TokenActionsComponent.Params( + data = model.tokenActionsData, + callbacks = model, + bottomAction = model.currentBottomAction, + isRedesignForced = true, + isCompact = isCompactTokenActions, + ), + ) + } + + private val chooseTokenComponent: ChooseTokenComponent? by lazy { + (params.launchMode as? AddFundsComponent.LaunchMode.ChooseToken)?.let { + chooseTokenComponentFactory.create( + context = child(key = "addFundsChooseToken"), + params = ChooseTokenComponent.Params( + bridge = model.chooseTokenBridge, + ), + ) + } + } + + private val userPortfolioComponent: UserPortfolioComponent by lazy { + userPortfolioComponentFactory.create( + context = child(key = "addFundsUserPortfolio"), + params = UserPortfolioComponent.Params( + uiState = model.userPortfolioStateController.uiState, + callbacks = object : UserPortfolioComponent.Callbacks { + override fun onContinueFromUserPortfolio() = Unit + }, + ), + ) + } + + override fun dismiss() = model.onDismiss() @Composable - override fun Content(modifier: Modifier) { - chooseTokenComponent.Content(modifier) - val isTokenActionsShown by model.isTokenActionsShown.collectAsStateWithLifecycle() - if (isTokenActionsShown) { - // force use redesign theme here according to the task requirements, will be reworked in the next release - TangemThemeRedesign { - TangemModalBottomSheet( - config = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = model::onTokenActionsDismiss, - content = TangemBottomSheetConfigContent.Empty, - ), - containerColor = TangemTheme.colors2.surface.level2, - scrollableContent = true, - title = { - TangemModalBottomSheetTitle( - modifier = Modifier.fillMaxWidth(), - title = resourceReference(R.string.common_get_token), - endIconRes = R.drawable.ic_close_24, - onEndClick = model::onTokenActionsDismiss, - ) - }, - content = { _ -> - Column( - modifier = Modifier.padding( - start = TangemTheme.dimens2.x4, - top = TangemTheme.dimens2.x2, - end = TangemTheme.dimens2.x4, - bottom = TangemTheme.dimens2.x4, - ), - ) { - tokenActionsComponent.Content(Modifier) - } - }, - ) - } + override fun BottomSheet() { + val route by model.uiRoute.collectAsStateWithLifecycle() + val canGoBack by model.canGoBack.collectAsStateWithLifecycle() + + LaunchedEffect(route) { + if (route != AddFundsModel.UiRoute.UserPortfolio) return@LaunchedEffect + val mode = params.launchMode as? AddFundsComponent.LaunchMode.FilteredByRawId ?: return@LaunchedEffect + model.userPortfolioStateController.updateAndWaitNotNullState( + allAvailableData = model.buildAvailableToAddDataForChooser(), + rawCurrencyId = mode.rawCurrencyId, + ) } + + WithOptionalRedesignTheme(isEnabled = isAddFundsStage1Enabled) { + TangemBottomSheet( + onBack = if (canGoBack) model::onBack else ::dismiss, + config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ), + type = when (params.launchMode) { + is AddFundsComponent.LaunchMode.TokenActionsOnly -> TangemBottomSheetType.Modal + is AddFundsComponent.LaunchMode.ChooseToken -> TangemBottomSheetType.Default + is AddFundsComponent.LaunchMode.FilteredByRawId -> + if (route is AddFundsModel.UiRoute.TokenActions) { + TangemBottomSheetType.Default + } else { + TangemBottomSheetType.Modal + } + }, + containerColor = TangemTheme.colors2.surface.level2, + title = { + AddFundsBottomSheetTitle( + route = route, + canGoBack = canGoBack, + onBackClick = model::onBack, + onCloseClick = ::dismiss, + ) + }, + content = { + val animatedContentModifier = + if (params.launchMode is AddFundsComponent.LaunchMode.ChooseToken) { + Modifier.fillMaxSize() + } else { + Modifier + } + AnimatedContent( + targetState = route, + modifier = animatedContentModifier, + label = "AddFundsContentAnimation", + ) { animatedRoute -> + AddFundsRouteContent( + route = animatedRoute, + shouldFillHeight = !isCompactTokenActions && animatedRoute.uiSpec().shouldFillHeight, + ) + } + }, + ) + } + } + + @Composable + private fun AddFundsRouteContent(route: AddFundsModel.UiRoute, shouldFillHeight: Boolean) { + val spec = route.uiSpec() + val horizontalPadding = if (spec.shouldApplyHorizontalPadding) { + Modifier.padding(horizontal = TangemTheme.dimens2.x4) + } else { + Modifier + } + val sizeModifier = if (shouldFillHeight) Modifier.fillMaxSize() else Modifier.fillMaxWidth() + RenderRoute(route, horizontalPadding.then(sizeModifier)) + } + + @Composable + private fun RenderRoute(route: AddFundsModel.UiRoute, modifier: Modifier = Modifier) { + when (route) { + AddFundsModel.UiRoute.Loading -> Unit + AddFundsModel.UiRoute.ChooseToken -> chooseTokenComponent?.Content(modifier) + AddFundsModel.UiRoute.UserPortfolio -> CompositionLocalProvider( + LocalTangemBottomSheetContentBottomInset provides TangemTheme.dimens2.x4, + ) { + userPortfolioComponent.Content(modifier) + } + AddFundsModel.UiRoute.TokenActions -> tokenActionsComponent.Content(modifier) + } + } + + @Composable + private fun AddFundsBottomSheetTitle( + route: AddFundsModel.UiRoute, + canGoBack: Boolean, + onBackClick: () -> Unit, + onCloseClick: () -> Unit, + ) { + TangemTopBar( + title = route.uiSpec().title, + type = TangemTopBarType.BottomSheet, + startContent = if (canGoBack) { + { CircleIconButton(iconRes = CoreR.drawable.ic_arrow_back_28, onClick = onBackClick) } + } else { + null + }, + endContent = { + CircleIconButton(iconRes = R.drawable.ic_close_24, onClick = onCloseClick) + }, + ) + } + + @Composable + private fun WithOptionalRedesignTheme(isEnabled: Boolean, content: @Composable () -> Unit) { + if (isEnabled) { + TangemThemeRedesign(content = content) + } else { + content() + } + } + + @Composable + private fun CircleIconButton(iconRes: Int, onClick: () -> Unit) { + Icon( + imageVector = ImageVector.vectorResource(id = iconRes), + contentDescription = null, + tint = TangemTheme.colors2.graphic.neutral.primary, + modifier = Modifier + .size(TangemTheme.dimens2.x11) + .background( + color = TangemTheme.colors2.button.backgroundSecondary, + shape = CircleShape, + ) + .clickableSingle(onClick = onClick) + .padding(TangemTheme.dimens2.x2), + ) } @AssistedFactory diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/analytics/AddFundsAnalyticsEvent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/analytics/AddFundsAnalyticsEvent.kt index 42f36992ed..86cb642022 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/analytics/AddFundsAnalyticsEvent.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/analytics/AddFundsAnalyticsEvent.kt @@ -19,8 +19,6 @@ internal sealed class AddFundsAnalyticsEvent( class ButtonReceive : AddFundsAnalyticsEvent(event = "Button - Receive") - class ButtonGoToToken : AddFundsAnalyticsEvent(event = "Button - Go to Token") - companion object { private const val CATEGORY = "Add Funds" const val SOURCE_MAIN_SCREEN = "Main Screen" diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/model/AddFundsModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/model/AddFundsModel.kt index b3404a4490..46b5a407bf 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/model/AddFundsModel.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/model/AddFundsModel.kt @@ -1,5 +1,6 @@ package com.tangem.features.commonfeatures.impl.addfunds.model +import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.ui.markets.action.CryptoCurrencyData @@ -8,14 +9,25 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2 +import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.account.filterCryptoPortfolio +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.commonfeatures.api.addfunds.AddFundsComponent +import com.tangem.features.commonfeatures.api.addtoportfolio.AvailableToAddData +import com.tangem.features.commonfeatures.api.addtoportfolio.AvailableToAddWallet import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenAnalyticsPayload import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult +import com.tangem.features.commonfeatures.api.tokenactions.BottomAction import com.tangem.features.commonfeatures.impl.addfunds.analytics.AddFundsAnalyticsEvent -import com.tangem.features.commonfeatures.impl.addtoportfolio.TokenActionsComponent +import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent +import com.tangem.features.commonfeatures.impl.userportfolio.state.UserPortfolioStateController import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* @@ -23,98 +35,257 @@ import kotlinx.coroutines.launch import javax.inject.Inject @ModelScoped +@Suppress("LongParameterList") internal class AddFundsModel @Inject constructor( paramsContainer: ParamsContainer, chooseTokenBridgeFactory: ChooseTokenBridge.Factory, + userPortfolioStateControllerFactory: UserPortfolioStateController.Factory, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCaseV2, - private val appRouter: AppRouter, + private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier, + private val getUserWalletUseCase: GetUserWalletUseCase, + private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val appRouter: AppRouter, override val dispatchers: CoroutineDispatcherProvider, ) : Model(), TokenActionsComponent.Callbacks { private val params = paramsContainer.require() + val launchMode: AddFundsComponent.LaunchMode = params.launchMode - val chooseTokenBridge: ChooseTokenBridge = chooseTokenBridgeFactory.create( - modelScope = modelScope, - settings = ChooseTokenBridge.Settings.AddFunds, - analyticsPayload = setOf( - ChooseTokenAnalyticsPayload.ScreensSources(SCREEN_SOURCE), - ), - ) + private val routeStack = MutableStateFlow(listOf(UiRoute.Loading)) - private val selectedToken = MutableStateFlow(null) + val uiRoute: StateFlow = routeStack + .map { it.last() } + .distinctUntilChanged() + .stateIn(modelScope, SharingStarted.Eagerly, initialValue = UiRoute.Loading) - val isTokenActionsShown: StateFlow = selectedToken - .map { it != null } + val canGoBack: StateFlow = routeStack + .map { it.size > 1 } + .distinctUntilChanged() .stateIn(modelScope, SharingStarted.Eagerly, initialValue = false) + private val tokenActionsTrigger = MutableStateFlow(null) + private val filteredEntries = MutableStateFlow>(emptyList()) + + val currentBottomAction: MutableStateFlow = + MutableStateFlow(BottomAction.None) + @OptIn(ExperimentalCoroutinesApi::class) - val tokenActionsData: Flow = selectedToken + val tokenActionsData: Flow = tokenActionsTrigger .filterNotNull() - .flatMapLatest { result -> - val cryptoPortfolio = result.account as? AccountStatus.CryptoPortfolio - ?: return@flatMapLatest emptyFlow() - getCryptoCurrencyActionsUseCase( - accountId = cryptoPortfolio.account.accountId, - currency = result.currency.currency, - ).map { actionsState -> + .flatMapLatest { request -> + combine( + getCryptoCurrencyActionsUseCase( + accountId = request.account.account.accountId, + currency = request.status.currency, + ), + isAccountsModeEnabledUseCase(), + ) { actionsState, isAccountMode -> CryptoCurrencyData( - userWallet = result.wallet, - status = result.currency, + userWallet = request.userWallet, + status = request.status, actions = actionsState.states, - isAccountMode = false, - account = cryptoPortfolio, + isAccountMode = isAccountMode, + account = request.account, ) } } + val chooseTokenBridge: ChooseTokenBridge by lazy { + chooseTokenBridgeFactory.create( + modelScope = modelScope, + settings = ChooseTokenBridge.Settings.AddFunds, + analyticsPayload = setOf(ChooseTokenAnalyticsPayload.ScreensSources(SCREEN_SOURCE)), + ) + } + + val userPortfolioStateController: UserPortfolioStateController = userPortfolioStateControllerFactory.create( + modelScope = modelScope, + onTokenSelected = { result -> + openTokenActions( + request = TokenActionsRequest( + userWallet = result.wallet, + account = result.account, + status = result.addedCurrency, + ), + bottomAction = BottomAction.None, + ) + }, + ) + init { - chooseTokenBridge.selectWalletTab(params.userWalletId) - analyticsEventHandler.send( - AddFundsAnalyticsEvent.MethodScreenOpened(source = AddFundsAnalyticsEvent.SOURCE_MAIN_SCREEN), - ) - observeBridge() + when (val mode = launchMode) { + is AddFundsComponent.LaunchMode.ChooseToken -> initChooseToken(mode) + is AddFundsComponent.LaunchMode.TokenActionsOnly -> initTokenActionsOnly(mode) + is AddFundsComponent.LaunchMode.FilteredByRawId -> initFilteredByRawId(mode) + } } - override fun onBottomActionClick() { - val result = selectedToken.value ?: return - selectedToken.value = null - analyticsEventHandler.send(AddFundsAnalyticsEvent.ButtonGoToToken()) - appRouter.replaceCurrent( - AppRoute.CurrencyDetails( - userWalletId = result.wallet.walletId, - currency = result.currency.currency, - ), - ) + fun onBack() { + routeStack.update { stack -> if (stack.size > 1) stack.dropLast(1) else stack } } - override fun onQuickActionClick(action: TokenActionsBSContentUM.Action) { + fun onDismiss() = params.onDismiss() + + override fun onBottomActionClick(bottomAction: BottomAction) { + val request = tokenActionsTrigger.value + if (bottomAction == BottomAction.GoToToken && request != null) { + appRouter.push( + AppRoute.CurrencyDetails( + userWalletId = request.userWallet.walletId, + currency = request.status.currency, + ), + ) + } + params.onDismiss() + } + + override fun onQuickActionClick(action: TokenActionsBSContentUM.Action, shouldDismiss: Boolean) { val event = when (action) { TokenActionsBSContentUM.Action.Buy -> AddFundsAnalyticsEvent.ButtonBuy() TokenActionsBSContentUM.Action.Exchange -> AddFundsAnalyticsEvent.ButtonSwap() TokenActionsBSContentUM.Action.Receive -> AddFundsAnalyticsEvent.ButtonReceive() - else -> return + else -> null + } + event?.let { analyticsEventHandler.send(it) } + if (shouldDismiss) { + params.onDismiss() } - analyticsEventHandler.send(event) } - fun onTokenActionsDismiss() { - selectedToken.value = null + fun buildAvailableToAddDataForChooser(): AvailableToAddData { + val byWallet = filteredEntries.value.groupBy { it.userWallet.walletId } + return AvailableToAddData( + availableToAddWallets = byWallet.mapValues { (_, entries) -> + AvailableToAddWallet( + userWallet = entries.first().userWallet, + accounts = entries.map { it.account }.distinct(), + availableNetworks = emptySet(), + availableToAddAccounts = emptyMap(), + ) + }, + ) } - private fun observeBridge() { + private fun initChooseToken(mode: AddFundsComponent.LaunchMode.ChooseToken) { + chooseTokenBridge.selectWalletTab(mode.userWalletId) + analyticsEventHandler.send( + AddFundsAnalyticsEvent.MethodScreenOpened(source = AddFundsAnalyticsEvent.SOURCE_MAIN_SCREEN), + ) + replaceRoot(UiRoute.ChooseToken) modelScope.launch { - chooseTokenBridge.onCurrencyChosen.receiveAsFlow().collect { result -> - selectedToken.value = result - } + chooseTokenBridge.onCurrencyChosen.receiveAsFlow().collect(::openTokenActionsFromBridge) } modelScope.launch { - chooseTokenBridge.onClose.receiveAsFlow().collect { - appRouter.pop() + chooseTokenBridge.onClose.receiveAsFlow().collect { params.onDismiss() } + } + } + + private fun initTokenActionsOnly(mode: AddFundsComponent.LaunchMode.TokenActionsOnly) { + modelScope.launch { + val wallet = getUserWalletUseCase.invokeFlow(mode.userWalletId) + .mapNotNull { it.getOrNull() } + .first() + val match = multiAccountStatusListSupplier() + .first() + .firstOrNull { it.userWalletId == mode.userWalletId } + ?.accountStatuses + ?.filterCryptoPortfolio() + ?.firstNotNullOfOrNull { accountStatus -> + accountStatus.tokenList.flattenCurrencies() + .firstOrNull { it.currency.id == mode.currency.id } + ?.let { accountStatus to it } + } + ?: run { + params.onDismiss() + return@launch + } + tokenActionsTrigger.value = TokenActionsRequest(wallet, match.first, match.second) + replaceRoot(UiRoute.TokenActions) + } + } + + private fun initFilteredByRawId(mode: AddFundsComponent.LaunchMode.FilteredByRawId) { + modelScope.launch { + val entries = collectFilteredEntries(mode.rawCurrencyId) + when (entries.size) { + 0 -> params.onDismiss() + 1 -> { + val entry = entries.first() + tokenActionsTrigger.value = TokenActionsRequest(entry.userWallet, entry.account, entry.status) + replaceRoot(UiRoute.TokenActions) + } + else -> { + filteredEntries.value = entries + replaceRoot(UiRoute.UserPortfolio) + } } } } + private suspend fun collectFilteredEntries(rawCurrencyId: CryptoCurrency.RawID): List { + val accountLists = multiAccountStatusListSupplier().first() + return accountLists.flatMap { accountStatusList -> + val wallet = getUserWalletUseCase.invokeFlow(accountStatusList.userWalletId) + .mapNotNull { it.getOrNull() } + .firstOrNull() + ?: return@flatMap emptyList() + accountStatusList.accountStatuses.filterCryptoPortfolio().flatMap { accountStatus -> + accountStatus.tokenList.flattenCurrencies() + .filter { status -> + val id = status.currency.id.rawCurrencyId ?: return@filter false + getTokenIdIfL2Network(id.value) == rawCurrencyId.value + } + .map { status -> FilteredEntry(wallet, accountStatus, status) } + } + } + } + + private fun openTokenActionsFromBridge(result: ChooseTokenResult) { + val account = result.account as? AccountStatus.CryptoPortfolio ?: return + openTokenActions( + request = TokenActionsRequest(result.wallet, account, result.currency), + bottomAction = if (result.wasJustAdded) { + BottomAction.GoToToken + } else { + BottomAction.None + }, + ) + } + + private fun openTokenActions(request: TokenActionsRequest, bottomAction: BottomAction) { + tokenActionsTrigger.value = request + currentBottomAction.value = bottomAction + pushRoute(UiRoute.TokenActions) + } + + private fun replaceRoot(route: UiRoute) { + routeStack.value = listOf(route) + } + + private fun pushRoute(route: UiRoute) { + routeStack.update { it + route } + } + + sealed interface UiRoute { + data object Loading : UiRoute + data object ChooseToken : UiRoute + data object UserPortfolio : UiRoute + data object TokenActions : UiRoute + } + + private data class TokenActionsRequest( + val userWallet: UserWallet, + val account: AccountStatus.CryptoPortfolio, + val status: CryptoCurrencyStatus, + ) + + private data class FilteredEntry( + val userWallet: UserWallet, + val account: AccountStatus.CryptoPortfolio, + val status: CryptoCurrencyStatus, + ) + private companion object { const val SCREEN_SOURCE = "AddFunds" } diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/model/AddFundsRouteUiSpec.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/model/AddFundsRouteUiSpec.kt new file mode 100644 index 0000000000..067e6b76a6 --- /dev/null +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addfunds/model/AddFundsRouteUiSpec.kt @@ -0,0 +1,35 @@ +package com.tangem.features.commonfeatures.impl.addfunds.model + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.commonfeatures.impl.R +import com.tangem.core.ui.R as CoreR + +internal data class AddFundsRouteUiSpec( + val title: TextReference, + val shouldApplyHorizontalPadding: Boolean, + val shouldFillHeight: Boolean, +) + +internal fun AddFundsModel.UiRoute.uiSpec(): AddFundsRouteUiSpec = when (this) { + AddFundsModel.UiRoute.Loading -> AddFundsRouteUiSpec( + title = resourceReference(R.string.common_add_funds), + shouldApplyHorizontalPadding = false, + shouldFillHeight = false, + ) + AddFundsModel.UiRoute.ChooseToken -> AddFundsRouteUiSpec( + title = resourceReference(R.string.common_add_funds), + shouldApplyHorizontalPadding = false, + shouldFillHeight = true, + ) + AddFundsModel.UiRoute.UserPortfolio -> AddFundsRouteUiSpec( + title = resourceReference(R.string.common_add_funds), + shouldApplyHorizontalPadding = false, + shouldFillHeight = false, + ) + AddFundsModel.UiRoute.TokenActions -> AddFundsRouteUiSpec( + title = resourceReference(CoreR.string.common_get_token), + shouldApplyHorizontalPadding = true, + shouldFillHeight = true, + ) +} \ No newline at end of file diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetFooter.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetFooter.kt index dac734d96d..5044f83a2c 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetFooter.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetFooter.kt @@ -23,7 +23,7 @@ import com.tangem.features.commonfeatures.impl.R import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioFooterKind import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes import com.tangem.features.commonfeatures.impl.addtoportfolio.model.uiSpec -import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM +import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioUM import dev.chrisbanes.haze.rememberHazeState @Composable diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetSwitch.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetSwitch.kt index 6cb218fbc1..5bb93a7721 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetSwitch.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetSwitch.kt @@ -6,7 +6,7 @@ import com.arkivanov.decompose.router.stack.ChildStack import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes -import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM +import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioUM @Composable internal fun AddToPortfolioBottomSheetSwitch( diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetV2.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetV2.kt index 89f444dc04..4bcc188921 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetV2.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/AddToPortfolioBottomSheetV2.kt @@ -22,7 +22,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.features.commonfeatures.impl.R import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes import com.tangem.features.commonfeatures.impl.addtoportfolio.model.uiSpec -import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM +import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioUM @Composable internal fun AddToPortfolioBottomSheetV2( @@ -39,6 +39,11 @@ internal fun AddToPortfolioBottomSheetV2( contentStack.value = stack } + val type = if (stack.active.configuration is AddToPortfolioRoutes.TokenActions) { + TangemBottomSheetType.Default + } else { + TangemBottomSheetType.Modal + } TangemBottomSheet( onBack = onBack, config = TangemBottomSheetConfig( @@ -46,7 +51,7 @@ internal fun AddToPortfolioBottomSheetV2( onDismissRequest = onDismiss, content = TangemBottomSheetConfigContent.Empty, ), - type = TangemBottomSheetType.Modal, + type = type, containerColor = TangemTheme.colors2.surface.level2, title = { AddToPortfolioBottomSheetTitle( @@ -86,7 +91,9 @@ private fun AddToPortfolioRouteContent(animatedStack: ChildStack replayMutableSharedFlow() = MutableSharedFlow( replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, @@ -155,7 +153,7 @@ internal class AddToPortfolioModel @Inject constructor( } } - @Suppress("LongMethod") + @Suppress("LongMethod", "CyclomaticComplexMethod") private fun startRedesignAddToPortfolioFlow() { channelFlow { fun finishSuccessFlow(result: AddToPortfolioManager.Result) { @@ -312,9 +310,15 @@ internal class AddToPortfolioModel @Inject constructor( .onEmpty { finishSuccessFlow(result) } .launchIn(this) - callbackDelegate.onChooseTokenBottomActionClick.receiveAsFlow().first() - analyticsEventHandler.send(eventBuilder.getTokenLater()) - finishSuccessFlow(result) + when (val meta = terminalTokenActionsFlow().first()) { + is AddToPortfolioManager.FinishMeta.OnBottomAction -> { + finishSuccessFlow(result.copy(meta = meta)) + } + AddToPortfolioManager.FinishMeta.OnQuickAction -> { + finishSuccessFlow(result.copy(meta = meta)) + } + AddToPortfolioManager.FinishMeta.None -> Unit + } } .catch { throwable -> TangemLogger.e("Error", throwable) @@ -323,6 +327,21 @@ internal class AddToPortfolioModel @Inject constructor( .launchIn(modelScope) } + private fun terminalTokenActionsFlow() = channelFlow { + callbackDelegate.onChooseTokenBottomActionClick.receiveAsFlow() + .onEach { bottomAction -> + channel.send(AddToPortfolioManager.FinishMeta.OnBottomAction(bottomAction)) + } + .launchIn(this) + callbackDelegate.onQuickActionClick.receiveAsFlow() + .onEach { (action, shouldDismiss) -> + analyticsEventHandler.send(eventBuilder.getTokenActionClick(actionUM = action)) + if (shouldDismiss) channel.send(AddToPortfolioManager.FinishMeta.OnQuickAction) + } + .launchIn(this) + awaitClose() + } + private suspend fun getInitialSelection( initialData: AvailableToAddData, ): AddToPortfolioInitialSelectionResolver.InitialSelection? { @@ -529,7 +548,8 @@ internal class AddToPortfolioCallbackDelegate @Inject constructor() : UserPortfolioComponent.Callbacks { val onNetworkSelected = Channel() - val onChooseTokenBottomActionClick = Channel() + val onChooseTokenBottomActionClick = Channel() + val onQuickActionClick = Channel>() val onChangeNetworkClick = Channel() val onChangePortfolioClick = Channel() val onTokenAdded = Channel() @@ -539,8 +559,12 @@ internal class AddToPortfolioCallbackDelegate @Inject constructor() : onNetworkSelected.trySend(network) } - override fun onBottomActionClick() { - onChooseTokenBottomActionClick.trySend(Unit) + override fun onBottomActionClick(bottomAction: BottomAction) { + onChooseTokenBottomActionClick.trySend(bottomAction) + } + + override fun onQuickActionClick(action: TokenActionsBSContentUM.Action, shouldDismiss: Boolean) { + onQuickActionClick.trySend(action to shouldDismiss) } override fun onChangeNetworkClick() { diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioRouteUiSpec.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioRouteUiSpec.kt index bd256958e8..68d861ed3a 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioRouteUiSpec.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioRouteUiSpec.kt @@ -44,7 +44,7 @@ internal fun AddToPortfolioRoutes.uiSpec(): AddToPortfolioRouteUiSpec = when (th ) AddToPortfolioRoutes.TokenActions -> AddToPortfolioRouteUiSpec( title = resourceReference(R.string.common_get_token), - isScrollable = true, + isScrollable = false, shouldApplyHorizontalPadding = true, footer = AddToPortfolioFooterKind.None, ) diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/DefaultChooseTokenComponent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/DefaultChooseTokenComponent.kt index 7a55c72de3..ca30d7ce6a 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/DefaultChooseTokenComponent.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/DefaultChooseTokenComponent.kt @@ -41,7 +41,7 @@ internal class DefaultChooseTokenComponent @AssistedInject constructor( override fun Content(modifier: Modifier) { val bottomSheet by bottomSheetSlot.subscribeAsState() val state by model.state.collectAsStateWithLifecycle() - ChooseTokenScreen(state = state) + ChooseTokenScreen(state = state, modifier = modifier) bottomSheet.child?.instance?.BottomSheet() } diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt index 97810d1fda..e4df6cb25f 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/model/ChooseTokenModel.kt @@ -6,6 +6,8 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.resourceReference +import com.tangem.features.commonfeatures.api.R +import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenAnalyticsPayload import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery @@ -14,10 +16,9 @@ import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenComponent import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult import com.tangem.features.commonfeatures.impl.choosetoken.converter.SearchBarToggleTransformer import com.tangem.features.commonfeatures.impl.choosetoken.converter.SearchBarUpdateQueryTransformer +import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketState import com.tangem.features.commonfeatures.impl.choosetoken.ui.ChooseTokenFullUM import com.tangem.features.commonfeatures.impl.choosetoken.ui.ChooseTokenInitialUM -import com.tangem.features.commonfeatures.api.R -import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketState import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import javax.inject.Inject @@ -78,18 +79,10 @@ internal class ChooseTokenModel @Inject constructor( .onEach { marketBlockDelegate.addToPortfolioSlot.dismiss() } .launchIn(modelScope) addToPortfolioManager.onSuccessAdded.receiveAsFlow() - .onEach { addedResult -> - val isSearched = ChooseTokenAnalyticsPayload.IsSearched(isSearchingState) - val isMarketToken = ChooseTokenAnalyticsPayload.IsMarketTokenSelected(true) - val chooseTokenResult = ChooseTokenResult( - currency = addedResult.addedCurrency, - account = addedResult.account, - wallet = addedResult.wallet, - analyticsPayload = setOf(isSearched, isMarketToken), - ) - bridge.onCurrencyChosen(chooseTokenResult) - marketBlockDelegate.addToPortfolioSlot.dismiss() - } + .onEach { notifyCurrencyChosen(it, isMarketTokenSelected = true) } + .launchIn(modelScope) + addToPortfolioManager.onAddedTokenClick.receiveAsFlow() + .onEach { notifyCurrencyChosen(it, isMarketTokenSelected = false) } .launchIn(modelScope) } @@ -97,6 +90,20 @@ internal class ChooseTokenModel @Inject constructor( bridge.onClose() } + private fun notifyCurrencyChosen(addedResult: AddToPortfolioManager.Result, isMarketTokenSelected: Boolean) { + val chooseTokenResult = ChooseTokenResult( + currency = addedResult.addedCurrency, + account = addedResult.account, + wallet = addedResult.wallet, + analyticsPayload = setOf( + ChooseTokenAnalyticsPayload.IsSearched(isSearchingState), + ChooseTokenAnalyticsPayload.IsMarketTokenSelected(isMarketTokenSelected), + ), + ) + bridge.onCurrencyChosen(chooseTokenResult) + marketBlockDelegate.addToPortfolioSlot.dismiss() + } + private fun getInitialSearchBar(): SearchBarUM = SearchBarUM( placeholderText = resourceReference(R.string.common_search), query = "", @@ -112,6 +119,7 @@ internal class ChooseTokenModel @Inject constructor( private fun getInitState() = ChooseTokenInitialUM( screenTitle = bridge.settings.title, + isAppBarShown = bridge.settings.isAppBarShown, onCloseClick = ::onBackClicked, searchBar = getInitialSearchBar(), ) diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenScreen.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenScreen.kt index 1db37d2b53..ec7b76be00 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenScreen.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenScreen.kt @@ -82,12 +82,14 @@ private val ChooseTokenFullUM.isEmptyState: Boolean internal fun ChooseTokenScreen(state: ChooseTokenFullUM, modifier: Modifier = Modifier) { Column( modifier = modifier - .background(color = TangemTheme.colors.background.secondary) + .background(color = TangemTheme.colors2.surface.level2) .fillMaxSize() .imePadding(), horizontalAlignment = Alignment.CenterHorizontally, ) { - AppBar(title = state.initialUM.screenTitle, onBackClick = state.initialUM.onCloseClick, Modifier) + if (state.initialUM.isAppBarShown) { + AppBar(title = state.initialUM.screenTitle, onBackClick = state.initialUM.onCloseClick, Modifier) + } Content( state = state, @@ -465,6 +467,7 @@ private val wallets private val initialUM = ChooseTokenInitialUM( screenTitle = stringReference("Choose token"), + isAppBarShown = true, onCloseClick = {}, searchBar = searchBar, ) diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenUM.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenUM.kt index 3cecafda97..5c6628cdf4 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenUM.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/choosetoken/ui/ChooseTokenUM.kt @@ -13,6 +13,7 @@ internal data class ChooseTokenFullUM( internal data class ChooseTokenInitialUM( val screenTitle: TextReference, + val isAppBarShown: Boolean, val onCloseClick: () -> Unit, val searchBar: SearchBarUM, ) \ No newline at end of file diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/TokenActionsComponent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/TokenActionsComponent.kt similarity index 83% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/TokenActionsComponent.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/TokenActionsComponent.kt index 7aff9aef21..5ad5e141d6 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/TokenActionsComponent.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/TokenActionsComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio +package com.tangem.features.commonfeatures.impl.tokenactions import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -18,14 +18,16 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.res.LocalRedesignEnabled import com.tangem.domain.models.TokenReceiveConfig -import com.tangem.features.commonfeatures.impl.addtoportfolio.model.TokenActionsModel -import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.TokenActionsContent -import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.TokenActionsContentV2 +import com.tangem.features.commonfeatures.api.tokenactions.BottomAction +import com.tangem.features.commonfeatures.impl.tokenactions.model.TokenActionsModel +import com.tangem.features.commonfeatures.impl.tokenactions.ui.TokenActionsContent +import com.tangem.features.commonfeatures.impl.tokenactions.ui.TokenActionsContentV2 import com.tangem.features.tokenreceive.TokenReceiveComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf internal class TokenActionsComponent @AssistedInject constructor( @Assisted context: AppComponentContext, @@ -74,15 +76,14 @@ internal class TokenActionsComponent @AssistedInject constructor( data class Params( val data: Flow, val callbacks: Callbacks, - val bottomAction: BottomAction = BottomAction.Later, + val bottomAction: Flow = flowOf(BottomAction.None), val isRedesignForced: Boolean = false, + val isCompact: Boolean = false, ) - enum class BottomAction { Later, GoToToken } - interface Callbacks { - fun onBottomActionClick() - fun onQuickActionClick(action: TokenActionsBSContentUM.Action) {} + fun onBottomActionClick(bottomAction: BottomAction) + fun onQuickActionClick(action: TokenActionsBSContentUM.Action, shouldDismiss: Boolean) {} } @AssistedFactory diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/TokenActionsModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/model/TokenActionsModel.kt similarity index 67% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/TokenActionsModel.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/model/TokenActionsModel.kt index 95b7c4fe7e..9dfca10ac8 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/TokenActionsModel.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/model/TokenActionsModel.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.model +package com.tangem.features.commonfeatures.impl.tokenactions.model import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate @@ -12,8 +12,8 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.models.TokenReceiveConfig import com.tangem.domain.transaction.usecase.ReceiveAddressesFactory -import com.tangem.features.commonfeatures.impl.addtoportfolio.TokenActionsComponent -import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM +import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent +import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.TokenActionsUM import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,7 +45,9 @@ internal class TokenActionsModel @Inject constructor( private val tokenActionsHandler: TokenActionsHandler = tokenActionsIntentsFactory.create( currentAppCurrency = Provider { currentAppCurrency.value }, - onHandleQuickAction = { handledAction -> handledQuickAction(handledAction) }, + onHandleQuickAction = { handledAction, shouldDismiss -> + handledQuickAction(handledAction, shouldDismiss) + }, ) val bottomSheetNavigation: SlotNavigation = SlotNavigation() @@ -55,15 +57,17 @@ internal class TokenActionsModel @Inject constructor( combine( params.data, getBalanceHidingSettingsUseCase.isBalanceHidden(), - ) { cryptoCurrencyData, isBalanceHidden -> - cryptoCurrencyData to isBalanceHidden + params.bottomAction, + ) { cryptoCurrencyData, isBalanceHidden, bottomAction -> + Triple(cryptoCurrencyData, isBalanceHidden, bottomAction) } - .mapLatest { (cryptoCurrencyData, isBalanceHidden) -> + .mapLatest { (cryptoCurrencyData, isBalanceHidden, bottomAction) -> uiBuilder.build( cryptoCurrencyData = cryptoCurrencyData, tokenActionsHandler = tokenActionsHandler, appCurrency = currentAppCurrency.value, isBalanceHidden = isBalanceHidden, + bottomAction = bottomAction, ) } .flowOn(dispatchers.default) @@ -73,16 +77,18 @@ internal class TokenActionsModel @Inject constructor( initialValue = null, ) - private fun handledQuickAction(handledAction: TokenActionsHandler.HandledQuickAction) = modelScope.launch { - params.callbacks.onQuickActionClick(handledAction.action) - val isReceive = handledAction.action == TokenActionsBSContentUM.Action.Receive - if (!isReceive) return@launch - val tokenConfig = withContext(dispatchers.default) { - receiveAddressesFactory.create( - status = handledAction.cryptoCurrencyData.status, - userWalletId = handledAction.cryptoCurrencyData.userWallet.walletId, - ) - } ?: return@launch - bottomSheetNavigation.activate(tokenConfig) - } + private fun handledQuickAction(handledAction: TokenActionsHandler.HandledQuickAction, shouldDismiss: Boolean) = + modelScope.launch { + val isReceive = handledAction.action == TokenActionsBSContentUM.Action.Receive + if (isReceive) { + val tokenConfig = withContext(dispatchers.default) { + receiveAddressesFactory.create( + status = handledAction.cryptoCurrencyData.status, + userWalletId = handledAction.cryptoCurrencyData.userWallet.walletId, + ) + } + if (tokenConfig != null) bottomSheetNavigation.activate(tokenConfig) + } + params.callbacks.onQuickActionClick(handledAction.action, shouldDismiss) + } } \ No newline at end of file diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/TokenActionsUiBuilder.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/model/TokenActionsUiBuilder.kt similarity index 89% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/TokenActionsUiBuilder.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/model/TokenActionsUiBuilder.kt index 0c59500c53..91c88bc364 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/TokenActionsUiBuilder.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/model/TokenActionsUiBuilder.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.model +package com.tangem.features.commonfeatures.impl.tokenactions.model import androidx.compose.ui.text.SpanStyle import com.tangem.common.getTotalCryptoAmount @@ -11,6 +11,7 @@ import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToI import com.tangem.common.ui.markets.action.CryptoCurrencyData import com.tangem.common.ui.markets.action.QuickActionsConverter.quickActions import com.tangem.common.ui.markets.action.TokenActionsHandler +import com.tangem.features.commonfeatures.api.tokenactions.BottomAction import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.ParamsContainer @@ -30,9 +31,9 @@ import com.tangem.features.commonfeatures.impl.R import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.wallets.usecase.GetWalletIconUseCase -import com.tangem.features.commonfeatures.impl.addtoportfolio.TokenActionsComponent -import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.PortfolioBadgeUM -import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM +import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent +import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.PortfolioBadgeUM +import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.TokenActionsUM import java.math.BigDecimal import javax.inject.Inject @@ -50,6 +51,7 @@ internal class TokenActionsUiBuilder @Inject constructor( tokenActionsHandler: TokenActionsHandler, appCurrency: AppCurrency, isBalanceHidden: Boolean, + bottomAction: BottomAction, ): TokenActionsUM { return if (designFeatureToggles.isRedesignEnabled || params.isRedesignForced) { buildV2( @@ -57,11 +59,13 @@ internal class TokenActionsUiBuilder @Inject constructor( tokenActionsHandler = tokenActionsHandler, appCurrency = appCurrency, isBalanceHidden = isBalanceHidden, + bottomAction = bottomAction, ) } else { buildV1( cryptoCurrencyData = cryptoCurrencyData, tokenActionsHandler = tokenActionsHandler, + bottomAction = bottomAction, ) } } @@ -69,6 +73,7 @@ internal class TokenActionsUiBuilder @Inject constructor( private fun buildV1( cryptoCurrencyData: CryptoCurrencyData, tokenActionsHandler: TokenActionsHandler, + bottomAction: BottomAction, ): TokenActionsUM { val status = cryptoCurrencyData.status val tokenUM = TokenItemState.Content( @@ -88,9 +93,9 @@ internal class TokenActionsUiBuilder @Inject constructor( tokenActionsHandler = tokenActionsHandler, isRedesignEnabled = false, ), - bottomActionText = bottomActionText(params.bottomAction), + bottomActionText = bottomActionText(bottomAction), onBottomActionClick = { - params.callbacks.onBottomActionClick() + params.callbacks.onBottomActionClick(bottomAction) }, ) } @@ -100,6 +105,7 @@ internal class TokenActionsUiBuilder @Inject constructor( tokenActionsHandler: TokenActionsHandler, appCurrency: AppCurrency, isBalanceHidden: Boolean, + bottomAction: BottomAction, ): TokenActionsUM { val status = cryptoCurrencyData.status val tokenUM = TokenItemState.Content( @@ -119,19 +125,20 @@ internal class TokenActionsUiBuilder @Inject constructor( tokenActionsHandler = tokenActionsHandler, isRedesignEnabled = true, ), - bottomActionText = bottomActionText(params.bottomAction), + bottomActionText = bottomActionText(bottomAction), onBottomActionClick = { - params.callbacks.onBottomActionClick() + params.callbacks.onBottomActionClick(bottomAction) }, isBalancesHidden = isBalanceHidden, portfolioBadge = createPortfolioBadge(cryptoCurrencyData = cryptoCurrencyData), + isCompact = params.isCompact, ) } - private fun bottomActionText(action: TokenActionsComponent.BottomAction): TextReference { + private fun bottomActionText(action: BottomAction): TextReference? { return when (action) { - TokenActionsComponent.BottomAction.Later -> resourceReference(R.string.common_later) - TokenActionsComponent.BottomAction.GoToToken -> resourceReference(R.string.common_go_to_token) + BottomAction.GoToToken -> resourceReference(R.string.common_go_to_token) + BottomAction.None -> null } } diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/TokenActionsContent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/TokenActionsContent.kt similarity index 94% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/TokenActionsContent.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/TokenActionsContent.kt index d8bb296eb7..98a8417126 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/TokenActionsContent.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/TokenActionsContent.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.ui +package com.tangem.features.commonfeatures.impl.tokenactions.ui import android.content.res.Configuration import androidx.compose.foundation.ExperimentalFoundationApi @@ -39,7 +39,7 @@ import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.commonfeatures.impl.R -import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM +import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.TokenActionsUM import kotlinx.collections.immutable.persistentListOf import java.util.UUID @@ -73,13 +73,15 @@ internal fun TokenActionsContent(state: TokenActionsUM, modifier: Modifier = Mod } } - SpacerH16() + if (state.bottomActionText != null) { + SpacerH16() - SecondaryButton( - modifier = Modifier.fillMaxWidth(), - text = state.bottomActionText.resolveReference(), - onClick = state.onBottomActionClick, - ) + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = state.bottomActionText.resolveReference(), + onClick = state.onBottomActionClick, + ) + } } } diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/TokenActionsContentV2.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/TokenActionsContentV2.kt similarity index 74% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/TokenActionsContentV2.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/TokenActionsContentV2.kt index da852d3217..f1df9cffe7 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/TokenActionsContentV2.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/TokenActionsContentV2.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.ui +package com.tangem.features.commonfeatures.impl.tokenactions.ui import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility @@ -43,8 +43,8 @@ import com.tangem.core.ui.format.bigdecimal.formatStyled import com.tangem.core.ui.format.bigdecimal.price import com.tangem.core.ui.res.* import com.tangem.features.commonfeatures.impl.R -import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.PortfolioBadgeUM -import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state.TokenActionsUM +import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.PortfolioBadgeUM +import com.tangem.features.commonfeatures.impl.tokenactions.ui.state.TokenActionsUM import dev.chrisbanes.haze.rememberHazeState import kotlinx.collections.immutable.persistentListOf import java.math.BigDecimal @@ -52,53 +52,84 @@ import java.util.UUID @Composable internal fun TokenActionsContentV2(state: TokenActionsUM, modifier: Modifier = Modifier) { + if (state.isCompact) { + CompactLayout(state, modifier) + } else { + FullLayout(state, modifier) + } +} + +@Composable +private fun CompactLayout(state: TokenActionsUM, modifier: Modifier = Modifier) { + Column(modifier = modifier.fillMaxWidth()) { + QuickActionsList(state) + SpacerH(TangemTheme.dimens2.x4) + } +} + +@Composable +private fun FullLayout(state: TokenActionsUM, modifier: Modifier = Modifier) { Column( - modifier = modifier.fillMaxWidth(), + modifier = modifier + .fillMaxSize() + .navigationBarsPadding(), ) { - TokenHeader( - addedToken = state.token, - portfolioBadge = state.portfolioBadge, - isBalanceHidden = state.isBalancesHidden, - ) - - SpacerH(TangemTheme.dimens2.x2) - - Column( - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2), + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + contentAlignment = Alignment.Center, ) { - state.quickActions.actions.fastForEach { actionUM -> - key(actionUM.title) { - val transitionState = remember { - MutableTransitionState(initialState = false).apply { targetState = true } - } - AnimatedVisibility( - visibleState = transitionState, - enter = fadeIn() + expandVertically(), - exit = fadeOut() + shrinkVertically(), - ) { - TokenActionRow( - iconRes = actionUM.icon, - title = actionUM.title, - description = actionUM.description, - onClick = { state.quickActions.onQuickActionClick(actionUM) }, - onLongClick = { state.quickActions.onQuickActionLongClick(actionUM) } - .takeIf { actionUM.isLongClickAvailable }, - ) - } - } + TokenHeader( + addedToken = state.token, + portfolioBadge = state.portfolioBadge, + isBalanceHidden = state.isBalancesHidden, + ) + } + QuickActionsList(state) + val bottomText = state.bottomActionText + if (bottomText != null) { + SpacerH(TangemTheme.dimens2.x6) + CompositionLocalProvider(LocalHazeState provides rememberHazeState()) { + SecondaryTangemButton( + modifier = Modifier.fillMaxWidth(), + onClick = state.onBottomActionClick, + text = bottomText, + size = TangemButtonSize.X12, + shape = TangemButtonShape.Rounded, + ) } } + SpacerH(TangemTheme.dimens2.x4) + } +} - SpacerH(TangemTheme.dimens2.x6) - - CompositionLocalProvider(LocalHazeState provides rememberHazeState()) { - SecondaryTangemButton( - modifier = Modifier.fillMaxWidth(), - onClick = state.onBottomActionClick, - text = state.bottomActionText, - size = TangemButtonSize.X12, - shape = TangemButtonShape.Rounded, - ) +@Composable +private fun QuickActionsList(state: TokenActionsUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2), + ) { + state.quickActions.actions.fastForEach { actionUM -> + key(actionUM.title) { + val transitionState = remember { + MutableTransitionState(initialState = false).apply { targetState = true } + } + AnimatedVisibility( + visibleState = transitionState, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), + ) { + TokenActionRow( + iconRes = actionUM.icon, + title = actionUM.title, + description = actionUM.description, + onClick = { state.quickActions.onQuickActionClick(actionUM) }, + onLongClick = { state.quickActions.onQuickActionLongClick(actionUM) } + .takeIf { actionUM.isLongClickAvailable }, + ) + } + } } } } diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/state/TokenActionsUM.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/state/TokenActionsUM.kt similarity index 84% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/state/TokenActionsUM.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/state/TokenActionsUM.kt index 0094208dd0..c797eae343 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/ui/state/TokenActionsUM.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/tokenactions/ui/state/TokenActionsUM.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.ui.state +package com.tangem.features.commonfeatures.impl.tokenactions.ui.state import androidx.compose.runtime.Immutable import com.tangem.common.ui.markets.action.QuickActions @@ -10,10 +10,11 @@ import com.tangem.core.ui.extensions.TextReference internal data class TokenActionsUM( val token: TokenItemState, val quickActions: QuickActions, - val bottomActionText: TextReference, + val bottomActionText: TextReference?, val onBottomActionClick: () -> Unit, val isBalancesHidden: Boolean = false, val portfolioBadge: PortfolioBadgeUM = PortfolioBadgeUM.None, + val isCompact: Boolean = false, ) @Immutable diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/DefaultUserPortfolioComponent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/DefaultUserPortfolioComponent.kt similarity index 89% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/DefaultUserPortfolioComponent.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/DefaultUserPortfolioComponent.kt index 42d36a8582..4f63615817 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/DefaultUserPortfolioComponent.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/DefaultUserPortfolioComponent.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio +package com.tangem.features.commonfeatures.impl.userportfolio import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -7,7 +7,7 @@ import com.tangem.common.ui.markets.tokenselector.TokenSelectorEmbeddedContent import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.components.bottomsheets.LocalTangemBottomSheetContentBottomInset -import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioModel +import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/UserPortfolioComponent.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/UserPortfolioComponent.kt similarity index 74% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/UserPortfolioComponent.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/UserPortfolioComponent.kt index b827f3568d..b2be7f4b4e 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/UserPortfolioComponent.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/UserPortfolioComponent.kt @@ -1,8 +1,8 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio +package com.tangem.features.commonfeatures.impl.userportfolio import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM +import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioUM import kotlinx.coroutines.flow.StateFlow internal interface UserPortfolioComponent : ComposableContentComponent { diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/model/UserPortfolioModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/model/UserPortfolioModel.kt similarity index 76% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/model/UserPortfolioModel.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/model/UserPortfolioModel.kt index 9bd839e9b9..238a3d88c2 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/model/UserPortfolioModel.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/model/UserPortfolioModel.kt @@ -1,9 +1,9 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model +package com.tangem.features.commonfeatures.impl.userportfolio.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.features.commonfeatures.impl.addtoportfolio.userportfolio.UserPortfolioComponent +import com.tangem.features.commonfeatures.impl.userportfolio.UserPortfolioComponent import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.StateFlow import javax.inject.Inject diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/model/UserPortfolioUM.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/model/UserPortfolioUM.kt similarity index 74% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/model/UserPortfolioUM.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/model/UserPortfolioUM.kt index bece060837..483a4fcabb 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/model/UserPortfolioUM.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/model/UserPortfolioUM.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model +package com.tangem.features.commonfeatures.impl.userportfolio.model import androidx.compose.runtime.Immutable import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/state/UserPortfolioStateController.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/state/UserPortfolioStateController.kt similarity index 89% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/state/UserPortfolioStateController.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/state/UserPortfolioStateController.kt index 386c9af2f7..cd3083ecc0 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/state/UserPortfolioStateController.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/state/UserPortfolioStateController.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.state +package com.tangem.features.commonfeatures.impl.userportfolio.state import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase @@ -7,8 +7,8 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.wallets.usecase.GetWalletIconUseCase import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager import com.tangem.features.commonfeatures.api.addtoportfolio.AvailableToAddData -import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM -import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.transformer.UserPortfolioSectionsTransformer +import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioUM +import com.tangem.features.commonfeatures.impl.userportfolio.transformer.UserPortfolioSectionsTransformer import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/transformer/UserPortfolioSectionsTransformer.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/transformer/UserPortfolioSectionsTransformer.kt similarity index 97% rename from features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/transformer/UserPortfolioSectionsTransformer.kt rename to features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/transformer/UserPortfolioSectionsTransformer.kt index 0a4a19aa57..f154bdd609 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/userportfolio/transformer/UserPortfolioSectionsTransformer.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/userportfolio/transformer/UserPortfolioSectionsTransformer.kt @@ -1,4 +1,4 @@ -package com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.transformer +package com.tangem.features.commonfeatures.impl.userportfolio.transformer import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network import com.tangem.common.ui.account.toUM @@ -21,7 +21,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager import com.tangem.features.commonfeatures.api.addtoportfolio.AvailableToAddData -import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioUM +import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioUM import com.tangem.utils.StringsSigns import com.tangem.utils.extensions.orZero import kotlinx.collections.immutable.toImmutableList diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt index 6c4dafd1a7..4e65f19449 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt @@ -22,11 +22,13 @@ import com.tangem.features.promobanners.api.PromoBannersBlockComponent import kotlinx.serialization.Serializable import javax.inject.Inject +@Suppress("LongParameterList") internal class FeedEntryChildFactory @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val portfolioComponentFactory: MarketsPortfolioComponent.Factory, private val portfolioBlockComponentFactory: PortfolioBlockComponent.Factory, private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory, + private val addFundsComponentFactory: com.tangem.features.commonfeatures.api.addfunds.AddFundsComponent.Factory, private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory, private val designFeatureToggles: DesignFeatureToggles, ) { @@ -81,6 +83,7 @@ internal class FeedEntryChildFactory @Inject constructor( portfolioBlockComponentFactory = portfolioBlockComponentFactory, designFeatureToggles = designFeatureToggles, addToPortfolioComponentFactory = addToPortfolioComponentFactory, + addFundsComponentFactory = addFundsComponentFactory, ) } is Child.TokenList -> { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/AddFundsSlotRoute.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/AddFundsSlotRoute.kt new file mode 100644 index 0000000000..dbbcbe112c --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/AddFundsSlotRoute.kt @@ -0,0 +1,10 @@ +package com.tangem.features.feed.components.market.details + +import com.tangem.core.decompose.navigation.Route +import com.tangem.domain.models.currency.CryptoCurrency +import kotlinx.serialization.Serializable + +@Serializable +internal data class AddFundsSlotRoute( + val rawCurrencyId: CryptoCurrency.RawID, +) : Route \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt index 9589c9646a..8d2cc3164c 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/DefaultMarketsTokenDetailsComponent.kt @@ -19,6 +19,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot +import com.arkivanov.decompose.router.slot.dismiss import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.context.AppComponentContext @@ -41,6 +42,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.markets.PreselectedTokenDetailsSection import com.tangem.domain.markets.TokenMarketParams import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.features.commonfeatures.api.addfunds.AddFundsComponent import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent @@ -64,6 +66,7 @@ internal class DefaultMarketsTokenDetailsComponent( portfolioBlockComponentFactory: PortfolioBlockComponent.Factory, val params: Params, private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory, + private val addFundsComponentFactory: AddFundsComponent.Factory, ) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext { // applying l2 compatibility @@ -101,6 +104,10 @@ internal class DefaultMarketsTokenDetailsComponent( override fun openAddToPortfolioViaUserPortfolio(rawCurrencyId: CryptoCurrency.RawID) { model.openAddToPortfolioViaUserPortfolio() } + + override fun openAddFunds(rawCurrencyId: CryptoCurrency.RawID) { + model.openAddFunds(rawCurrencyId) + } }, ) } else { @@ -114,6 +121,14 @@ internal class DefaultMarketsTokenDetailsComponent( childFactory = ::addToPortfolioChild, ) + private val addFundsSlot = childSlot( + source = model.addFundsSheetNavigation, + serializer = AddFundsSlotRoute.serializer(), + key = "addFundsSlot", + handleBackButton = false, + childFactory = ::addFundsChild, + ) + init { componentScope.launch(dispatchers.default) { model.networksState.collectLatest { state -> @@ -155,6 +170,20 @@ internal class DefaultMarketsTokenDetailsComponent( ) } + private fun addFundsChild( + config: AddFundsSlotRoute, + componentContext: ComponentContext, + ): ComposableBottomSheetComponent { + val launchMode = AddFundsComponent.LaunchMode.FilteredByRawId(rawCurrencyId = config.rawCurrencyId) + return addFundsComponentFactory.create( + context = childByContext(componentContext), + params = AddFundsComponent.Params( + launchMode = launchMode, + onDismiss = { model.addFundsSheetNavigation.dismiss() }, + ), + ) + } + @Composable override fun Title(bottomSheetState: State) { val state by model.state.collectAsStateWithLifecycle() @@ -226,6 +255,7 @@ internal class DefaultMarketsTokenDetailsComponent( } val state by model.state.collectAsStateWithLifecycle() val bottomSheet by addToPortfolioSlot.subscribeAsState() + val addFundsBs by addFundsSlot.subscribeAsState() val bsState by bottomSheetState LaunchedEffect(bsState) { model.isVisibleOnScreen.value = bsState == BottomSheetState.EXPANDED @@ -248,6 +278,7 @@ internal class DefaultMarketsTokenDetailsComponent( }, ) bottomSheet.child?.instance?.BottomSheet() + addFundsBs.child?.instance?.BottomSheet() } @Serializable diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt index b98f118a73..444eff2564 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt @@ -5,6 +5,8 @@ import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter import com.tangem.common.ui.markets.action.TokenActionsBSContentUM import com.tangem.common.ui.markets.action.TokenActionsHandler import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -36,6 +38,7 @@ internal class MarketsPortfolioModel @Inject constructor( private val tokenActionsHandlerFactory: TokenActionsHandler.Factory, private val receiveAddressesFactory: ReceiveAddressesFactory, private val analyticsEventHandler: AnalyticsEventHandler, + private val appRouter: AppRouter, override val dispatchers: CoroutineDispatcherProvider, ) : Model() { @@ -68,6 +71,17 @@ internal class MarketsPortfolioModel @Inject constructor( addToPortfolioManager.onSuccessAdded.receiveAsFlow() .onEach { bottomSheetNavigation.dismiss() } .launchIn(modelScope) + addToPortfolioManager.onAddedTokenClick.receiveAsFlow() + .onEach { result -> + bottomSheetNavigation.dismiss() + appRouter.push( + AppRoute.CurrencyDetails( + userWalletId = result.wallet.walletId, + currency = result.addedCurrency.currency, + ), + ) + } + .launchIn(modelScope) } fun setTokenNetworks(networks: List) { @@ -127,7 +141,7 @@ internal class MarketsPortfolioModel @Inject constructor( private fun createTokenActionsHandler(): TokenActionsHandler { return tokenActionsHandlerFactory.create( currentAppCurrency = Provider { currentAppCurrency.value }, - onHandleQuickAction = { handledAction -> + onHandleQuickAction = { handledAction, _ -> val currency = handledAction.cryptoCurrencyData.status.currency analyticsEventHandler.send( analyticsEventBuilder.quickActionClick( diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/PortfolioBlockParentClickIntents.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/PortfolioBlockParentClickIntents.kt index 8be21c2648..d81fdd957b 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/PortfolioBlockParentClickIntents.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/PortfolioBlockParentClickIntents.kt @@ -5,4 +5,5 @@ import com.tangem.domain.models.currency.CryptoCurrency internal interface PortfolioBlockParentClickIntents { fun openAddToPortfolioDirect() fun openAddToPortfolioViaUserPortfolio(rawCurrencyId: CryptoCurrency.RawID) + fun openAddFunds(rawCurrencyId: CryptoCurrency.RawID) } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockModel.kt index 873efb8c79..6b5bb8bdef 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolioblock/model/PortfolioBlockModel.kt @@ -171,7 +171,7 @@ internal class PortfolioBlockModel @Inject constructor( tokenSymbol = firstCurrency.symbol, isBalanceHidden = isBalanceHidden, onRowClick = { parentRouter?.openAddToPortfolioViaUserPortfolio(currencyRawId) }, - onAddFundsClick = {}, + onAddFundsClick = { parentRouter?.openAddFunds(currencyRawId) }, ) } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt index a9d942b51e..b8b2361bed 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/market/details/MarketsTokenDetailsModel.kt @@ -48,6 +48,8 @@ import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager +import com.tangem.features.commonfeatures.api.tokenactions.BottomAction +import com.tangem.features.feed.components.market.details.AddFundsSlotRoute import com.tangem.features.feed.components.market.details.AddToPortfolioSlotRoute import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent import com.tangem.features.feed.components.market.details.analytics.MarketTokenAnalyticsEvent @@ -233,6 +235,7 @@ internal class MarketsTokenDetailsModel @Inject constructor( val networksState = MutableStateFlow(TokenNetworksState.Loading) val addToPortfolioSheetNavigation = SlotNavigation() + val addFundsSheetNavigation = SlotNavigation() private val isAddToPortfolioAvailable: Boolean = params.shouldShowPortfolio && designFeatureToggles.isRedesignEnabled @@ -345,7 +348,15 @@ internal class MarketsTokenDetailsModel @Inject constructor( .onEach { addToPortfolioSheetNavigation.dismiss() } .launchIn(modelScope) addToPortfolioManager.onSuccessAdded.receiveAsFlow() - .onEach { addToPortfolioSheetNavigation.dismiss() } + .onEach { result -> + addToPortfolioSheetNavigation.dismiss() + val meta = result.meta + if (meta is AddToPortfolioManager.FinishMeta.OnBottomAction && + meta.action == BottomAction.GoToToken + ) { + openTokenDetails(result) + } + } .launchIn(modelScope) addToPortfolioManager.onAddedTokenClick.receiveAsFlow() .onEach { result -> @@ -367,6 +378,10 @@ internal class MarketsTokenDetailsModel @Inject constructor( addToPortfolioSheetNavigation.activate(AddToPortfolioSlotRoute) } + fun openAddFunds(rawCurrencyId: com.tangem.domain.models.currency.CryptoCurrency.RawID) { + addFundsSheetNavigation.activate(AddFundsSlotRoute(rawCurrencyId = rawCurrencyId)) + } + private fun openTokenDetails(result: AddToPortfolioManager.Result) { appRouter.push( AppRoute.CurrencyDetails( diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index bd81777717..f84bcd7839 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -112,6 +112,7 @@ dependencies { implementation(projects.features.sendV2.api) implementation(projects.features.tokenRecieve.api) implementation(projects.features.yieldSupply.api) + implementation(projects.features.commonFeatures.api) implementation(deps.decompose.ext.compose) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt index a193fd3f49..46ced8cd7f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/DefaultTokenDetailsComponent.kt @@ -21,8 +21,8 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDeta import com.tangem.feature.tokendetails.presentation.tokendetails.route.TokenDetailsBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreenLegacy -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.AddFundsBottomSheetComponent import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.ChooseAddressBottomSheetComponent +import com.tangem.features.commonfeatures.api.addfunds.AddFundsComponent import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.CloreMigrationBottomSheetComponent import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.DynamicAddressesBottomSheetComponent import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.TransferBottomSheetComponent @@ -47,6 +47,7 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor( expressTransactionsComponentFactory: ExpressTransactionsComponent.Factory, private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory, private val yieldSupplyWarningComponentFactory: YieldSupplyDepositedWarningComponent.Factory, + private val addFundsComponentFactory: AddFundsComponent.Factory, yieldSupplyComponentFactory: YieldSupplyComponent.Factory, private val ratingComponentFactory: RatingComponent.Factory, ) : TokenDetailsComponent, AppComponentContext by appComponentContext { @@ -177,9 +178,15 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor( dynamicAddressesDelegate = model.dynamicAddressesDelegate, onDismiss = model.bottomSheetNavigation::dismiss, ) - is TokenDetailsBottomSheetConfig.AddFunds -> AddFundsBottomSheetComponent( - stateFlow = model.addFundsUiState, - onDismiss = model.bottomSheetNavigation::dismiss, + is TokenDetailsBottomSheetConfig.AddFunds -> addFundsComponentFactory.create( + context = childByContext(componentContext), + params = AddFundsComponent.Params( + launchMode = AddFundsComponent.LaunchMode.TokenActionsOnly( + userWalletId = route.userWalletId, + currency = route.currency, + ), + onDismiss = model.bottomSheetNavigation::dismiss, + ), ) is TokenDetailsBottomSheetConfig.Transfer -> TransferBottomSheetComponent( stateFlow = model.transferUiState, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index fb46837cc7..d1ecefea55 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -559,7 +559,12 @@ internal class TokenDetailsModel @Inject constructor( } override fun onAddFundsClick() { - bottomSheetNavigation.activate(TokenDetailsBottomSheetConfig.AddFunds) + bottomSheetNavigation.activate( + TokenDetailsBottomSheetConfig.AddFunds( + userWalletId = userWalletId, + currency = cryptoCurrency, + ), + ) } override fun onTransferClick() { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/route/TokenDetailsBottomSheetConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/route/TokenDetailsBottomSheetConfig.kt index e3ddd4ecb6..031ba31949 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/route/TokenDetailsBottomSheetConfig.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/route/TokenDetailsBottomSheetConfig.kt @@ -4,6 +4,7 @@ import com.tangem.core.decompose.navigation.Route import com.tangem.domain.models.TokenReceiveConfig import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.NetworkAddress +import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.model.details.TokenAction import kotlinx.serialization.Serializable @@ -32,7 +33,10 @@ sealed class TokenDetailsBottomSheetConfig : Route { data object DynamicAddresses : TokenDetailsBottomSheetConfig() @Serializable - data object AddFunds : TokenDetailsBottomSheetConfig() + data class AddFunds( + val userWalletId: UserWalletId, + val currency: CryptoCurrency, + ) : TokenDetailsBottomSheetConfig() @Serializable data object Transfer : TokenDetailsBottomSheetConfig() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/bottomsheet/AddFundsBottomSheetComponent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/bottomsheet/AddFundsBottomSheetComponent.kt deleted file mode 100644 index 4e06d5b4a9..0000000000 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/bottomsheet/AddFundsBottomSheetComponent.kt +++ /dev/null @@ -1,59 +0,0 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet - -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle -import com.tangem.core.ui.decompose.ComposableBottomSheetComponent -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.tokendetails.presentation.tokendetails.state.AddFundsUM -import kotlinx.coroutines.flow.StateFlow -import com.tangem.core.ui.R as CoreR - -internal class AddFundsBottomSheetComponent( - private val stateFlow: StateFlow, - private val onDismiss: () -> Unit, -) : ComposableBottomSheetComponent { - - override fun dismiss() { - onDismiss() - } - - @Composable - override fun BottomSheet() { - val state by stateFlow.collectAsStateWithLifecycle() - - val config = remember(state) { - TangemBottomSheetConfig( - isShown = true, - onDismissRequest = ::dismiss, - content = state, - ) - } - - TangemModalBottomSheet( - config = config, - containerColor = TangemTheme.colors2.surface.level2, - title = { - TangemModalBottomSheetTitle( - title = resourceReference(CoreR.string.common_get_token), - endIconRes = CoreR.drawable.ic_close_24, - onEndClick = ::dismiss, - ) - }, - content = { contentState -> - AddFundsBottomSheetContent( - state = contentState, - onCloseClick = ::dismiss, - modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x4), - ) - }, - ) - } -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/bottomsheet/AddFundsBottomSheetContent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/bottomsheet/AddFundsBottomSheetContent.kt deleted file mode 100644 index 6bb230a8cd..0000000000 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/bottomsheet/AddFundsBottomSheetContent.kt +++ /dev/null @@ -1,167 +0,0 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -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.tokenaction.TokenActionRow -import com.tangem.core.ui.components.SpacerH -import com.tangem.core.ui.ds.button.SecondaryTangemButton -import com.tangem.core.ui.ds.button.TangemButtonShape -import com.tangem.core.ui.ds.button.TangemButtonSize -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.res.LocalHazeState -import com.tangem.core.ui.res.LocalRedesignEnabled -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreviewRedesign -import com.tangem.feature.tokendetails.presentation.tokendetails.state.AddFundsUM -import dev.chrisbanes.haze.rememberHazeState -import com.tangem.core.ui.R as CoreR - -@Composable -internal fun AddFundsBottomSheetContent(state: AddFundsUM, onCloseClick: () -> Unit, modifier: Modifier = Modifier) { - Column( - modifier = modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2), - ) { - BuyActionRow(state = state) - SwapActionRow(state = state) - ReceiveActionRow(state = state) - - SpacerH(TangemTheme.dimens2.x2) - - CompositionLocalProvider(LocalHazeState provides rememberHazeState()) { - SecondaryTangemButton( - modifier = Modifier.fillMaxWidth(), - onClick = onCloseClick, - text = resourceReference(CoreR.string.common_close), - size = TangemButtonSize.X12, - shape = TangemButtonShape.Rounded, - ) - } - - SpacerH(TangemTheme.dimens2.x4) - } -} - -@Composable -private fun BuyActionRow(state: AddFundsUM) { - val row = (state as? AddFundsUM.Content)?.buy - if (state is AddFundsUM.Content && row == null) return - ActionRow( - iconRes = CoreR.drawable.ic_credit_card_20, - title = resourceReference(CoreR.string.common_buy), - description = resourceReference(CoreR.string.quick_action_buy_description), - row = row, - isLoading = state is AddFundsUM.Loading, - ) -} - -@Composable -private fun SwapActionRow(state: AddFundsUM) { - val row = (state as? AddFundsUM.Content)?.swap - if (state is AddFundsUM.Content && row == null) return - ActionRow( - iconRes = CoreR.drawable.ic_exchange_mini_24, - title = resourceReference(CoreR.string.common_swap), - description = resourceReference(CoreR.string.quick_action_swap_description), - row = row, - isLoading = state is AddFundsUM.Loading, - ) -} - -@Composable -private fun ReceiveActionRow(state: AddFundsUM) { - val row = (state as? AddFundsUM.Content)?.receive - if (state is AddFundsUM.Content && row == null) return - ActionRow( - iconRes = CoreR.drawable.ic_qrcode_new_24, - title = resourceReference(CoreR.string.common_receive), - description = resourceReference(CoreR.string.quick_action_receive_description), - row = row, - isLoading = state is AddFundsUM.Loading, - ) -} - -@Composable -private fun ActionRow( - iconRes: Int, - title: TextReference, - description: TextReference, - row: AddFundsUM.Row?, - isLoading: Boolean, -) { - if (isLoading || row?.isLoading == true) { - TokenActionRow( - iconRes = iconRes, - title = title, - description = description, - tailContent = { TailLoader() }, - ) - } else { - TokenActionRow( - iconRes = iconRes, - title = title, - description = description, - onClick = row?.onClick, - onLongClick = row?.onLongClick, - isEnabled = row?.isEnabled == true, - ) - } -} - -@Composable -private fun TailLoader() { - CircularProgressIndicator( - modifier = Modifier.size(20.dp), - color = TangemTheme.colors2.graphic.neutral.tertiary, - strokeWidth = 2.dp, - ) -} - -// region Preview -@Preview(widthDp = 360, showBackground = true) -@Composable -private fun Preview(@PreviewParameter(AddFundsPreviewProvider::class) state: AddFundsUM) { - TangemThemePreviewRedesign { - CompositionLocalProvider(LocalRedesignEnabled provides true) { - AddFundsBottomSheetContent( - state = state, - onCloseClick = {}, - modifier = Modifier.padding(horizontal = 16.dp), - ) - } - } -} - -private class AddFundsPreviewProvider : PreviewParameterProvider { - override val values: Sequence = sequenceOf( - AddFundsUM.Loading, - AddFundsUM.Content( - buy = AddFundsUM.Row(isLoading = false, isEnabled = true, onClick = {}), - swap = AddFundsUM.Row(isLoading = false, isEnabled = true, onClick = {}), - receive = AddFundsUM.Row(isLoading = false, isEnabled = true, onClick = {}, onLongClick = {}), - ), - AddFundsUM.Content( - buy = AddFundsUM.Row(isLoading = false, isEnabled = false, onClick = {}), - swap = AddFundsUM.Row(isLoading = false, isEnabled = false, onClick = {}), - receive = AddFundsUM.Row(isLoading = false, isEnabled = true, onClick = {}, onLongClick = {}), - ), - AddFundsUM.Content( - buy = null, - swap = null, - receive = AddFundsUM.Row(isLoading = false, isEnabled = true, onClick = {}, onLongClick = {}), - ), - ) -} -// endregion \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt index 2ae29d7123..e3a9cee814 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/WalletComponent.kt @@ -25,6 +25,7 @@ import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.tokens.model.details.TokenAction import com.tangem.feature.wallet.child.managetokens.AddAndManageBottomSheetComponent import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent +import com.tangem.features.commonfeatures.api.addfunds.AddFundsComponent import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent import com.tangem.feature.wallet.child.tokenActions.DefaultTokenActionsComponent import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent @@ -67,6 +68,7 @@ internal class WalletComponent @AssistedInject constructor( private val networkSelectionComponentFactory: NetworkSelectionComponent.Factory, private val tokenActionsComponentFactory: TokenActionsComponent.Factory, private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory, + private val addFundsComponentFactory: AddFundsComponent.Factory, private val designFeatureToggles: DesignFeatureToggles, ) : ComposableContentComponent, AppComponentContext by appComponentContext { @@ -180,6 +182,15 @@ internal class WalletComponent @AssistedInject constructor( ), ) } + is WalletDialogConfig.AddFunds -> { + addFundsComponentFactory.create( + context = childByContext(componentContext), + params = AddFundsComponent.Params( + launchMode = AddFundsComponent.LaunchMode.ChooseToken(dialogConfig.userWalletId), + onDismiss = model.innerWalletRouter.dialogNavigation::dismiss, + ), + ) + } is WalletDialogConfig.AddAndManage -> { AddAndManageBottomSheetComponent( appComponentContext = childByContext(componentContext), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index ab1ae71b4a..3a66c7a075 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -120,7 +120,9 @@ internal class DefaultWalletRouter @Inject constructor( } override fun openAddFunds(userWalletId: UserWalletId) { - router.push(AppRoute.AddFunds(userWalletId = userWalletId)) + dialogNavigation.activate( + configuration = WalletDialogConfig.AddFunds(userWalletId = userWalletId), + ) } override fun isWalletLastScreen(): Boolean { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt index 442b0f1268..afa05fb850 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletDialogConfig.kt @@ -62,6 +62,9 @@ internal sealed interface WalletDialogConfig { val customerId: String, ) : WalletDialogConfig + @Serializable + data class AddFunds(val userWalletId: UserWalletId) : WalletDialogConfig + @Serializable data class OrganizeTokens(val userWalletId: UserWalletId) : WalletDialogConfig