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 a200fea92f..afff976cd4 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 @@ -138,6 +138,7 @@ internal class ChildFactory @Inject constructor( AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES AppRoute.ManageTokens.Source.ACCOUNT -> ManageTokensSource.ACCOUNT + AppRoute.ManageTokens.Source.WALLET -> ManageTokensSource.WALLET } val mode = route.accountId?.let { ManageTokensMode.Account(it) } ?: ManageTokensMode.None 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 ea5ec5fdcc..6fba5a36d5 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 @@ -146,6 +146,7 @@ sealed class AppRoute(val path: String) : Route { STORIES, SETTINGS, ACCOUNT, + WALLET, } } diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 0154a6d8a0..5efc71652f 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -79,5 +79,9 @@ { "name": "SOLANA_TX_HISTORY_ENABLED", "version": "undefined" + }, + { + "name": "ADD_AND_MANAGE_TOKENS_ENABLED", + "version": "undefined" } ] diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt index fcce6daea7..dc51c35411 100644 --- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt @@ -9,6 +9,7 @@ enum class ManageTokensSource(val analyticsName: String) { ONBOARDING(analyticsName = "Onboarding"), SETTINGS(analyticsName = "Wallet Settings"), ACCOUNT(analyticsName = "Account"), + WALLET(analyticsName = "Wallet"), SEND_VIA_SWAP(analyticsName = "SendViaSwap"), } diff --git a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt index 1bb167f64c..1b13fbb3ad 100644 --- a/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt +++ b/features/wallet/api/src/main/kotlin/com/tangem/features/wallet/featuretoggles/WalletFeatureToggles.kt @@ -10,4 +10,6 @@ interface WalletFeatureToggles { val isWalletReorderFeatureEnabled: Boolean val isMainScreenQrScanningEnabled: Boolean + + val isAddAndManageTokensEnabled: Boolean } \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 70e66ddf64..c91c083d7d 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -128,6 +128,7 @@ dependencies { implementation(projects.domain.assetsdiscovery) /** Feature Apis */ + implementation(projects.features.account.api) implementation(projects.features.details.api) implementation(projects.features.hotWallet.api) implementation(projects.features.manageTokens.api) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/AddAndManageBottomSheetComponent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/AddAndManageBottomSheetComponent.kt new file mode 100644 index 0000000000..3cd335e5c5 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/AddAndManageBottomSheetComponent.kt @@ -0,0 +1,67 @@ +package com.tangem.feature.wallet.child.managetokens + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.slot.childSlot +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.feature.wallet.child.managetokens.model.AddAndManageModel +import com.tangem.feature.wallet.child.managetokens.ui.AddAndManageBottomSheetContent +import com.tangem.features.account.PortfolioSelectorComponent +import kotlinx.serialization.builtins.serializer + +internal class AddAndManageBottomSheetComponent( + appComponentContext: AppComponentContext, + private val params: Params, + private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory, +) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext { + + private val model: AddAndManageModel = getOrCreateModel(params) + + private val portfolioSelectorSlot = childSlot( + source = model.portfolioSelectorNavigation, + serializer = Unit.serializer(), + handleBackButton = false, + childFactory = { _, context -> portfolioSelectorChild(context) }, + ) + + private fun portfolioSelectorChild(componentContext: ComponentContext): ComposableBottomSheetComponent = + portfolioSelectorComponentFactory.create( + context = childByContext(componentContext), + params = PortfolioSelectorComponent.Params( + portfolioFetcher = model.portfolioFetcher, + controller = model.portfolioSelectorController, + bsCallback = model.portfolioSelectorCallback, + ), + ) + + override fun dismiss() { + params.onDismiss() + } + + @Composable + override fun BottomSheet() { + val portfolioSelectorSlot by portfolioSelectorSlot.subscribeAsState() + + AddAndManageBottomSheetContent( + onAddTokensClick = model::onAddTokensClick, + onOrganizeTokensClick = model::onOrganizeTokensClick, + onDismiss = ::dismiss, + ) + + portfolioSelectorSlot.child?.instance?.BottomSheet() + } + + data class Params( + val userWalletId: UserWalletId, + val onDismiss: () -> Unit, + val onOrganizeTokensClick: () -> Unit, + val onManageTokensClick: (AccountId) -> Unit, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/di/AddAndManageModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/di/AddAndManageModule.kt new file mode 100644 index 0000000000..5ce1fdfab7 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/di/AddAndManageModule.kt @@ -0,0 +1,20 @@ +package com.tangem.feature.wallet.child.managetokens.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.feature.wallet.child.managetokens.model.AddAndManageModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(ModelComponent::class) +internal interface AddAndManageModule { + + @Binds + @IntoMap + @ClassKey(AddAndManageModel::class) + fun bindAddAndManageModel(model: AddAndManageModel): Model +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/model/AddAndManageModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/model/AddAndManageModel.kt new file mode 100644 index 0000000000..13df43dd76 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/model/AddAndManageModel.kt @@ -0,0 +1,83 @@ +package com.tangem.feature.wallet.child.managetokens.model + +import com.arkivanov.decompose.router.slot.SlotNavigation +import com.arkivanov.decompose.router.slot.activate +import com.arkivanov.decompose.router.slot.dismiss +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.models.account.AccountId +import com.tangem.feature.wallet.child.managetokens.AddAndManageBottomSheetComponent +import com.tangem.features.account.PortfolioFetcher +import com.tangem.features.account.PortfolioSelectorComponent +import com.tangem.features.account.PortfolioSelectorController +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ModelScoped +internal class AddAndManageModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val portfolioFetcherFactory: PortfolioFetcher.Factory, + val portfolioSelectorController: PortfolioSelectorController, +) : Model() { + + private val params = paramsContainer.require() + + val portfolioSelectorNavigation: SlotNavigation = SlotNavigation() + + val portfolioFetcher: PortfolioFetcher by lazy { + portfolioFetcherFactory.create( + mode = PortfolioFetcher.Mode.Wallet(params.userWalletId), + scope = modelScope, + ) + } + + val portfolioSelectorCallback = object : PortfolioSelectorComponent.BottomSheetCallback { + override val onDismiss: () -> Unit = { portfolioSelectorNavigation.dismiss() } + override val onBack: () -> Unit = { portfolioSelectorNavigation.dismiss() } + } + + init { + observeAccountSelection() + } + + fun onAddTokensClick() { + modelScope.launch { + val data = portfolioFetcher.data.first() + val isSingleAccount = data.isSingleChoice(params.userWalletId) + + if (isSingleAccount) { + val mainAccountId = data.balances[params.userWalletId] + ?.accountsBalance + ?.mainAccount + ?.accountId + ?: AccountId.forMainCryptoPortfolio(params.userWalletId) + + params.onDismiss() + params.onManageTokensClick(mainAccountId) + } else { + portfolioSelectorNavigation.activate(Unit) + } + } + } + + fun onOrganizeTokensClick() { + params.onDismiss() + params.onOrganizeTokensClick() + } + + private fun observeAccountSelection() { + modelScope.launch { + portfolioSelectorController.selectedAccount.collect { accountId -> + if (accountId != null) { + portfolioSelectorNavigation.dismiss() + params.onDismiss() + params.onManageTokensClick(accountId) + } + } + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/ui/AddAndManageBottomSheetContent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/ui/AddAndManageBottomSheetContent.kt new file mode 100644 index 0000000000..5534c0cb4f --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/managetokens/ui/AddAndManageBottomSheetContent.kt @@ -0,0 +1,159 @@ +package com.tangem.feature.wallet.child.managetokens.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.res.R as ResR +import com.tangem.core.ui.R +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.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview + +@Composable +internal fun AddAndManageBottomSheetContent( + onAddTokensClick: () -> Unit, + onOrganizeTokensClick: () -> Unit, + onDismiss: () -> Unit, +) { + val config = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = onDismiss, + content = AddAndManageBottomSheetConfigContent, + ) + + TangemModalBottomSheet( + config = config, + containerColor = TangemTheme.colors.background.primary, + title = { + TangemModalBottomSheetTitle( + title = resourceReference(ResR.string.main_add_and_manage_tokens), + endIconRes = R.drawable.ic_close_24, + onEndClick = onDismiss, + ) + }, + content = { + AddAndManageContent( + onAddTokensClick = onAddTokensClick, + onOrganizeTokensClick = onOrganizeTokensClick, + ) + }, + ) +} + +@Composable +private fun AddAndManageContent(onAddTokensClick: () -> Unit, onOrganizeTokensClick: () -> Unit) { + Column( + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + bottom = 16.dp, + ), + ) { + AddAndManageRow( + iconRes = R.drawable.ic_plus_24, + title = ResR.string.add_and_manage_sheet_manage_title, + subtitle = ResR.string.add_and_manage_sheet_manage_subtitle, + onClick = onAddTokensClick, + modifier = Modifier.roundedShapeItemDecoration( + currentIndex = 0, + lastIndex = 1, + addDefaultPadding = false, + backgroundColor = TangemTheme.colors.background.action, + ), + ) + AddAndManageRow( + iconRes = R.drawable.ic_filter_default_24, + title = ResR.string.add_and_manage_sheet_organize_title, + subtitle = ResR.string.add_and_manage_sheet_organize_subtitle, + onClick = onOrganizeTokensClick, + modifier = Modifier.roundedShapeItemDecoration( + currentIndex = 1, + lastIndex = 1, + addDefaultPadding = false, + backgroundColor = TangemTheme.colors.background.action, + ), + ) + } +} + +@Composable +private fun AddAndManageRow( + iconRes: Int, + title: Int, + subtitle: Int, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 12.dp, vertical = 15.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(TangemTheme.colors.icon.accent.copy(alpha = 0.1f)), + ) { + Icon( + modifier = Modifier.size(18.dp), + painter = rememberVectorPainter(ImageVector.vectorResource(id = iconRes)), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + } + Column( + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + Text( + text = stringResourceSafe(id = title), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + Text( + text = stringResourceSafe(id = subtitle), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + } + } +} + +private object AddAndManageBottomSheetConfigContent : TangemBottomSheetConfigContent + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun AddAndManageBottomSheetContent_Preview() { + TangemThemePreview { + AddAndManageContent( + onAddTokensClick = {}, + onOrganizeTokensClick = {}, + ) + } +} +// 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 f15fbdfb75..72e1976879 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 @@ -23,7 +23,9 @@ import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.decompose.ComposableDialogComponent 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.account.PortfolioSelectorComponent import com.tangem.feature.wallet.child.tokenActions.DefaultTokenActionsComponent import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent import com.tangem.feature.wallet.child.wallet.model.WalletModel @@ -64,6 +66,7 @@ internal class WalletComponent @AssistedInject constructor( private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles, private val networkSelectionComponentFactory: NetworkSelectionComponent.Factory, private val tokenActionsComponentFactory: TokenActionsComponent.Factory, + private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory, private val designFeatureToggles: DesignFeatureToggles, ) : ComposableContentComponent, AppComponentContext by appComponentContext { @@ -178,6 +181,25 @@ internal class WalletComponent @AssistedInject constructor( ), ) } + is WalletDialogConfig.AddAndManage -> { + AddAndManageBottomSheetComponent( + appComponentContext = childByContext(componentContext), + params = AddAndManageBottomSheetComponent.Params( + userWalletId = dialogConfig.userWalletId, + onDismiss = model.innerWalletRouter.dialogNavigation::dismiss, + onOrganizeTokensClick = { + model.innerWalletRouter.openOrganizeTokensScreen(dialogConfig.userWalletId) + }, + onManageTokensClick = { accountId -> + model.innerWalletRouter.openManageTokensScreen( + accountId = accountId, + source = AppRoute.ManageTokens.Source.WALLET, + ) + }, + ), + portfolioSelectorComponentFactory = portfolioSelectorComponentFactory, + ) + } is WalletDialogConfig.OrganizeTokens -> { OrganizeTokensComponent( appComponentContext = childByContext(componentContext), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index 8f880da28c..71220bf8a9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -36,6 +36,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBot import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.collectLatest @@ -112,6 +113,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase, private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val uiMessageSender: UiMessageSender, + private val walletFeatureToggles: WalletFeatureToggles, ) : BaseWalletClickIntents(), WalletContentClickIntents { override fun onDetailsClick() { @@ -119,7 +121,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } override fun onOrganizeTokensClick() { - router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId()) + val userWalletId = stateHolder.getSelectedWalletId() + if (walletFeatureToggles.isAddAndManageTokensEnabled) { + router.openAddAndManageBottomSheet(userWalletId = userWalletId) + } else { + router.openOrganizeTokensScreen(userWalletId = userWalletId) + } } override fun onDismissMarketsTooltip() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt index d1ee8ec418..0120f51987 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggles/DefaultWalletFeatureToggles.kt @@ -14,4 +14,7 @@ internal class DefaultWalletFeatureToggles @Inject constructor( override val isMainScreenQrScanningEnabled: Boolean get() = featureToggles.isFeatureEnabled(FeatureToggles.MAIN_SCREEN_QR_SCANNING_ENABLED) + + override val isAddAndManageTokensEnabled: Boolean + get() = featureToggles.isFeatureEnabled(FeatureToggles.ADD_AND_MANAGE_TOKENS_ENABLED) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewDataLegacy.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewDataLegacy.kt index c52c371251..bbdd063032 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewDataLegacy.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewDataLegacy.kt @@ -91,6 +91,8 @@ internal object WalletScreenPreviewDataLegacy { ), ), organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( + textRes = R.string.organize_tokens_title, + iconRes = R.drawable.ic_filter_24, isEnabled = true, onClick = {}, ), @@ -119,6 +121,8 @@ internal object WalletScreenPreviewDataLegacy { ), ), organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( + textRes = R.string.organize_tokens_title, + iconRes = R.drawable.ic_filter_24, isEnabled = true, onClick = {}, ), @@ -149,6 +153,8 @@ internal object WalletScreenPreviewDataLegacy { ), ), organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( + textRes = R.string.organize_tokens_title, + iconRes = R.drawable.ic_filter_24, isEnabled = true, onClick = {}, ), 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 1eadd2b07b..962bd6549c 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 @@ -32,6 +32,7 @@ import javax.inject.Inject /** Default implementation of wallet feature router */ @ModelScoped +@Suppress("TooManyFunctions") internal class DefaultWalletRouter @Inject constructor( private val router: AppRouter, private val urlOpener: UrlOpener, @@ -66,14 +67,20 @@ internal class DefaultWalletRouter @Inject constructor( ) } - override fun openManageTokensScreen(accountId: AccountId) { + override fun openManageTokensScreen(accountId: AccountId, source: AppRoute.ManageTokens.Source) { val route = AppRoute.ManageTokens( - source = AppRoute.ManageTokens.Source.ACCOUNT, + source = source, accountId = accountId, ) router.push(route) } + override fun openAddAndManageBottomSheet(userWalletId: UserWalletId) { + dialogNavigation.activate( + configuration = WalletDialogConfig.AddAndManage(userWalletId = userWalletId), + ) + } + override fun openOnboardingScreen(scanResponse: ScanResponse, continueBackup: Boolean) { router.push( AppRoute.Onboarding( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index 7d772e539c..597093c071 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.SharedFlow [REDACTED_AUTHOR] */ @Stable +@Suppress("TooManyFunctions") internal interface InnerWalletRouter { val dialogNavigation: SlotNavigation @@ -47,7 +48,13 @@ internal interface InnerWalletRouter { fun openDetailsScreen(selectedWalletId: UserWalletId) /** Open manage tokens screen */ - fun openManageTokensScreen(accountId: AccountId) + fun openManageTokensScreen( + accountId: AccountId, + source: AppRoute.ManageTokens.Source = AppRoute.ManageTokens.Source.ACCOUNT, + ) + + /** Open add and manage tokens bottom sheet */ + fun openAddAndManageBottomSheet(userWalletId: UserWalletId) /** Open onboarding screen */ fun openOnboardingScreen(scanResponse: ScanResponse, continueBackup: Boolean = false) 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 8322d1c8df..52cb3834c7 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 @@ -50,6 +50,9 @@ internal sealed interface WalletDialogConfig { @Serializable data class KycRejected(val walletId: UserWalletId, val customerId: String) : WalletDialogConfig + @Serializable + data class AddAndManage(val userWalletId: UserWalletId) : WalletDialogConfig + @Serializable data class OrganizeTokens(val userWalletId: UserWalletId) : WalletDialogConfig diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt index 82050bd5fb..69cb21f25d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletTokensListState.kt @@ -49,5 +49,10 @@ internal sealed class WalletTokensListState { } } - data class OrganizeTokensButtonConfig(val isEnabled: Boolean, val onClick: () -> Unit) + data class OrganizeTokensButtonConfig( + val textRes: Int, + val iconRes: Int, + val isEnabled: Boolean, + val onClick: () -> Unit, + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index f3d9013ff0..ef84cc13c6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -25,6 +25,7 @@ internal class SetTokenListTransformer( private val shouldShowMainPromo: Boolean, private val isAccountsModeEnabled: Boolean, private val isRedesignEnabled: Boolean, + private val isAddAndManageTokensEnabled: Boolean, ) : WalletStateTransformer(userWallet.walletId) { private val tangemPayConverter by lazy { @@ -105,6 +106,7 @@ internal class SetTokenListTransformer( yieldModuleApyMap = yieldSupplyApyMap, stakingAvailabilityMap = stakingAvailabilityMap, shouldShowMainPromo = shouldShowMainPromo, + isAddAndManageTokensEnabled = isAddAndManageTokensEnabled, ).convert(value = this) } @@ -137,6 +139,7 @@ internal class SetTokenListTransformer( shouldShowMainPromo = shouldShowMainPromo, isAccountsModeEnabled = isAccountsModeEnabled, expandedAccounts = params.expandedAccounts, + isAddAndManageTokensEnabled = isAddAndManageTokensEnabled, ).convert(value = params.accountList) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index 926fdead7a..8d23a44a9c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -41,6 +41,7 @@ internal class TokenListStateConverter( private val yieldModuleApyMap: Map, private val stakingAvailabilityMap: Map, private val shouldShowMainPromo: Boolean, + private val isAddAndManageTokensEnabled: Boolean, ) : Converter { private val yieldSupplyPromoBannerConverter = YieldSupplyPromoBannerConverter( @@ -157,6 +158,8 @@ internal class TokenListStateConverter( } return if (currenciesSize > 1 && !isSingleCurrencyWalletWithToken()) { WalletOrganizeTokensButtonConfig( + textRes = organizeButtonTextRes(), + iconRes = organizeButtonIconRes(), isEnabled = tokenList.totalFiatBalance !is TotalFiatBalance.Loading, onClick = clickIntents::onOrganizeTokensClick, ) @@ -168,6 +171,8 @@ internal class TokenListStateConverter( private fun getOrganizeTokensButtonStateV2(accountList: AccountStatusList): WalletOrganizeTokensButtonConfig? { return if (accountList.flattenCurrencies().size > 1 && !isSingleCurrencyWalletWithToken()) { WalletOrganizeTokensButtonConfig( + textRes = organizeButtonTextRes(), + iconRes = organizeButtonIconRes(), isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading, onClick = clickIntents::onOrganizeTokensClick, ) @@ -176,6 +181,18 @@ internal class TokenListStateConverter( } } + private fun organizeButtonTextRes(): Int = if (isAddAndManageTokensEnabled) { + R.string.main_add_and_manage_tokens + } else { + R.string.organize_tokens_title + } + + private fun organizeButtonIconRes(): Int = if (isAddAndManageTokensEnabled) { + R.drawable.ic_filter_default_24 + } else { + R.drawable.ic_filter_24 + } + private fun isSingleCurrencyWalletWithToken(): Boolean { return selectedWallet is UserWallet.Cold && selectedWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt index c252326e14..a5535584a4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt @@ -38,6 +38,7 @@ internal class WalletTokensListUMConverter( private val isAccountsModeEnabled: Boolean, private val expandedAccounts: Set, private val stakingAvailabilityMap: Map, + private val isAddAndManageTokensEnabled: Boolean, shouldShowMainPromo: Boolean, ) : Converter { @@ -164,15 +165,25 @@ internal class WalletTokensListUMConverter( } private fun getOrganizeButtonUM(accountList: AccountStatusList): TangemButtonUM? { + val textRes = if (isAddAndManageTokensEnabled) { + R.string.main_add_and_manage_tokens + } else { + R.string.organize_tokens_title + } + val iconRes = if (isAddAndManageTokensEnabled) { + R.drawable.ic_filter_default_24 + } else { + R.drawable.ic_filter_24 + } return if (accountList.flattenCurrencies().size > 1 && !selectedWallet.isSingleWalletWithToken()) { TangemButtonUM( - text = resourceReference(R.string.organize_tokens_title), + text = resourceReference(textRes), isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading, size = TangemButtonSize.X9, shape = TangemButtonShape.Rounded, type = TangemButtonType.PrimaryInverse, tangemIconUM = TangemIconUM.Icon( - iconRes = R.drawable.ic_filter_default_24, + iconRes = iconRes, tintReference = { if (accountList.totalFiatBalance !is TotalFiatBalance.Loading) { TangemTheme.colors2.graphic.neutral.primary diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt index 2d7ba38899..a440c34a7f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/AccountListSubscriber.kt @@ -12,6 +12,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoU import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.utils.coroutines.combine7 import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -39,8 +40,12 @@ internal class AccountListSubscriber @AssistedInject constructor( private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase, private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase, private val designFeatureToggles: DesignFeatureToggles, + private val walletFeatureToggles: WalletFeatureToggles, ) : BasicAccountListSubscriber() { + override val isAddAndManageTokensEnabled: Boolean + get() = walletFeatureToggles.isAddAndManageTokensEnabled + override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7( flow1 = getAccountStatusListFlow(), flow2 = getAppCurrencyFlow(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt index 6192849dc4..047b5d765d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicAccountListSubscriber.kt @@ -34,6 +34,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { abstract val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase abstract val stateController: WalletStateController abstract val clickIntents: WalletClickIntents + abstract val isAddAndManageTokensEnabled: Boolean override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier get() = accountDependencies.singleAccountStatusListSupplier @@ -105,6 +106,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { shouldShowMainPromo = shouldShowMainPromo, isAccountsModeEnabled = isAccountMode, isRedesignEnabled = true, + isAddAndManageTokensEnabled = isAddAndManageTokensEnabled, ), ) } @@ -168,6 +170,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() { shouldShowMainPromo = shouldShowMainPromo, isAccountsModeEnabled = false, isRedesignEnabled = false, + isAddAndManageTokensEnabled = isAddAndManageTokensEnabled, ), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletSubscriber.kt index 88e0697c7e..ca409e79c6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletSubscriber.kt @@ -5,6 +5,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -18,8 +19,12 @@ internal class SingleWalletSubscriber @AssistedInject constructor( override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, override val stateController: WalletStateController, override val clickIntents: WalletClickIntents, + private val walletFeatureToggles: WalletFeatureToggles, ) : BasicAccountListSubscriber() { + override val isAddAndManageTokensEnabled: Boolean + get() = walletFeatureToggles.isAddAndManageTokensEnabled + override fun create(coroutineScope: CoroutineScope): Flow = combine( flow = getAccountStatusListFlow(), flow2 = getAppCurrencyFlow(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenSubscriberLegacy.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenSubscriberLegacy.kt index 1a31f1f1b4..0ad3c442ba 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenSubscriberLegacy.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenSubscriberLegacy.kt @@ -5,6 +5,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -19,8 +20,12 @@ internal class SingleWalletWithTokenSubscriberLegacy @AssistedInject constructor override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, override val stateController: WalletStateController, override val clickIntents: WalletClickIntents, + private val walletFeatureToggles: WalletFeatureToggles, ) : BasicAccountListSubscriber() { + override val isAddAndManageTokensEnabled: Boolean + get() = walletFeatureToggles.isAddAndManageTokensEnabled + override fun create(coroutineScope: CoroutineScope): Flow = combine( flow = getAccountStatusListFlow(), flow2 = getAppCurrencyFlow(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 8f842b82cf..1dcf79012d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -726,17 +726,13 @@ private fun WalletSnackbarHost( } internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modifier) { - (state as? WalletState.MultiCurrency)?.let { - (state.tokensListState as? WalletTokensListState.ContentState)?.let { - it.organizeTokensButtonConfig?.let { config -> - organizeTokensButton( - modifier = itemModifier, - isEnabled = config.isEnabled, - onClick = config.onClick, - ) - } - } - } + val multiCurrencyState = state as? WalletState.MultiCurrency ?: return + val contentState = multiCurrencyState.tokensListState as? WalletTokensListState.ContentState ?: return + val config = contentState.organizeTokensButtonConfig ?: return + organizeTokensButton( + modifier = itemModifier, + config = config, + ) } internal fun LazyListScope.nftCollections(state: WalletState, itemModifier: Modifier) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt index 94592fc246..6e5177817c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/multicurrency/MultiCurrencyOrganizeButton.kt @@ -7,7 +7,7 @@ import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig import com.tangem.core.ui.components.buttons.actions.RoundedActionButton import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.test.MainScreenTestTags -import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState private const val ORGANIZE_BUTTON_CONTENT_TYPE = "OrganizeTokensButton" @@ -20,18 +20,17 @@ private const val ORGANIZE_BUTTON_CONTENT_TYPE = "OrganizeTokensButton" [REDACTED_AUTHOR] */ internal fun LazyListScope.organizeTokensButton( - isEnabled: Boolean, - onClick: () -> Unit, + config: WalletTokensListState.OrganizeTokensButtonConfig, modifier: Modifier = Modifier, ) { item(key = ORGANIZE_BUTTON_CONTENT_TYPE, contentType = ORGANIZE_BUTTON_CONTENT_TYPE) { RoundedActionButton( modifier = modifier.testTag(MainScreenTestTags.ORGANIZE_TOKENS_BUTTON), config = ActionButtonConfig( - text = resourceReference(id = R.string.organize_tokens_title), - iconResId = R.drawable.ic_filter_24, - onClick = onClick, - isEnabled = isEnabled, + text = resourceReference(id = config.textRes), + iconResId = config.iconRes, + onClick = config.onClick, + isEnabled = config.isEnabled, ), ) }