Updated on 2026-08-14
This commit is contained in:
commit
d6f9f59866
1729 changed files with 67614 additions and 9361 deletions
|
|
@ -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<Params, AddFundsComponent>
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.commonfeatures.api.choosetoken.model.ChooseTokenPortf
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface ChooseTokenBridge : ChooseTokenBridgeInternal {
|
||||
|
|
@ -29,6 +30,12 @@ interface ChooseTokenBridge : ChooseTokenBridgeInternal {
|
|||
val title: TextReference,
|
||||
val isShowMarketBlock: Boolean,
|
||||
val isShowPaymentAccount: Boolean,
|
||||
val isAppBarShown: Boolean = true,
|
||||
/**
|
||||
* When `true`, single-currency wallets (and single-currency-with-token wallets like NODL)
|
||||
* are shown as selectable tabs. Swap flows keep this `false` — only multi-currency wallets apply there.
|
||||
*/
|
||||
val isShowSingleCurrencyWallets: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
val SwapFrom = Settings(
|
||||
|
|
@ -45,6 +52,8 @@ interface ChooseTokenBridge : ChooseTokenBridgeInternal {
|
|||
title = resourceReference(R.string.swapping_to_title),
|
||||
isShowMarketBlock = true,
|
||||
isShowPaymentAccount = false,
|
||||
isAppBarShown = false,
|
||||
isShowSingleCurrencyWallets = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -67,6 +76,9 @@ interface ChooseTokenBridgeInternal {
|
|||
val searchQueryState: StateFlow<SearchQuery>
|
||||
val fullPortfolioBlock: StateFlow<ChooseTokenPortfolioFullBlockUM?>
|
||||
|
||||
/** Currently selected wallet tab. Used to constrain feature blocks (e.g. market block) to the wallet's type. */
|
||||
val selectedWalletFlow: SharedFlow<UserWallet>
|
||||
|
||||
fun onSearchQuery(query: SearchQuery)
|
||||
fun onSearchQuery(query: String) = onSearchQuery(SearchQuery(query))
|
||||
|
||||
|
|
@ -90,6 +102,11 @@ data class ChooseTokenResult(
|
|||
val analyticsPayload: Set<ChooseTokenAnalyticsPayload> = emptySet(),
|
||||
) {
|
||||
val walletId get() = wallet.walletId
|
||||
|
||||
val wasJustAdded: Boolean
|
||||
get() = analyticsPayload
|
||||
.filterIsInstance<ChooseTokenAnalyticsPayload.IsMarketTokenSelected>()
|
||||
.any { it.value }
|
||||
}
|
||||
|
||||
sealed interface ChooseTokenAnalyticsPayload {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.features.commonfeatures.api.tokenactions
|
||||
|
||||
enum class BottomAction { GoToToken, None }
|
||||
|
|
@ -10,11 +10,6 @@ plugins {
|
|||
android {
|
||||
namespace = "com.tangem.features.commonfeatures.impl"
|
||||
}
|
||||
|
||||
tasks.withType<Test>().configureEach {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Api */
|
||||
implementation(projects.features.commonFeatures.api)
|
||||
|
|
@ -86,7 +81,6 @@ dependencies {
|
|||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(projects.common.test)
|
||||
testImplementation(projects.test.core)
|
||||
testImplementation(projects.test.mock)
|
||||
|
|
|
|||
|
|
@ -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<TangemBottomSheetConfigContent.Empty>(
|
||||
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<TangemBottomSheetConfigContent.Empty>(
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<AddFundsComponent.Params>()
|
||||
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>(UiRoute.Loading))
|
||||
|
||||
private val selectedToken = MutableStateFlow<ChooseTokenResult?>(null)
|
||||
val uiRoute: StateFlow<UiRoute> = routeStack
|
||||
.map { it.last() }
|
||||
.distinctUntilChanged()
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = UiRoute.Loading)
|
||||
|
||||
val isTokenActionsShown: StateFlow<Boolean> = selectedToken
|
||||
.map { it != null }
|
||||
val canGoBack: StateFlow<Boolean> = routeStack
|
||||
.map { it.size > 1 }
|
||||
.distinctUntilChanged()
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = false)
|
||||
|
||||
private val tokenActionsTrigger = MutableStateFlow<TokenActionsRequest?>(null)
|
||||
private val filteredEntries = MutableStateFlow<List<FilteredEntry>>(emptyList())
|
||||
|
||||
val currentBottomAction: MutableStateFlow<BottomAction> =
|
||||
MutableStateFlow(BottomAction.None)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val tokenActionsData: Flow<CryptoCurrencyData> = selectedToken
|
||||
val tokenActionsData: Flow<CryptoCurrencyData> = 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,
|
||||
userWallet = request.userWallet,
|
||||
status = actionsState.cryptoCurrencyStatus,
|
||||
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<FilteredEntry> {
|
||||
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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<TangemBottomSheetConfigContent.Empty>(
|
||||
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<AddToPortfolioR
|
|||
Spacer(modifier = Modifier.height(scrollBottomReserve))
|
||||
}
|
||||
} else {
|
||||
animatedStack.active.instance.Content(modifier = baseModifier)
|
||||
val isFullScreenRoute = animatedStack.active.configuration is AddToPortfolioRoutes.TokenActions
|
||||
val sizeModifier = if (isFullScreenRoute) Modifier.fillMaxSize() else Modifier
|
||||
animatedStack.active.instance.Content(modifier = baseModifier.then(sizeModifier))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioModel
|
||||
import com.tangem.features.commonfeatures.api.tokenactions.BottomAction
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioRoutes
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.UserPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent
|
||||
import com.tangem.features.commonfeatures.impl.userportfolio.UserPortfolioComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultAddToPortfolioComponent @AssistedInject constructor(
|
||||
|
|
@ -60,6 +63,7 @@ internal class DefaultAddToPortfolioComponent @AssistedInject constructor(
|
|||
params = TokenActionsComponent.Params(
|
||||
callbacks = model,
|
||||
data = model.tokenActionsData,
|
||||
bottomAction = flowOf(BottomAction.GoToToken),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,13 +92,5 @@ internal class PortfolioAnalyticsEvent(
|
|||
if (source != null) put("Source", source)
|
||||
},
|
||||
)
|
||||
|
||||
fun getTokenLater() = PortfolioAnalyticsEvent(
|
||||
event = "Popup Get token - Button Later",
|
||||
category = category,
|
||||
params = buildMap {
|
||||
if (source != null) put("Source", source)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioCompo
|
|||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.DefaultAddToPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.ui.DefaultAddToPortfolioManager
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.DefaultUserPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.UserPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.impl.userportfolio.DefaultUserPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.impl.userportfolio.UserPortfolioComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddToPortfolioModel
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddTokenModel
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.ChooseNetworkModel
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.model.TokenActionsModel
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.model.UserPortfolioModel
|
||||
import com.tangem.features.commonfeatures.impl.tokenactions.model.TokenActionsModel
|
||||
import com.tangem.features.commonfeatures.impl.userportfolio.model.UserPortfolioModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
|
|||
|
|
@ -29,18 +29,20 @@ import com.tangem.domain.models.wallet.isMultiCurrency
|
|||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.*
|
||||
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorController
|
||||
import com.tangem.features.commonfeatures.api.tokenactions.BottomAction
|
||||
import com.tangem.features.commonfeatures.impl.R
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.AddTokenComponent
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.ChooseNetworkComponent
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.TokenActionsComponent
|
||||
import com.tangem.features.commonfeatures.impl.tokenactions.TokenActionsComponent
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.analytics.PortfolioAnalyticsEvent
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.UserPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.impl.addtoportfolio.userportfolio.state.UserPortfolioStateController
|
||||
import com.tangem.features.commonfeatures.impl.userportfolio.UserPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.impl.userportfolio.state.UserPortfolioStateController
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -107,10 +109,6 @@ internal class AddToPortfolioModel @Inject constructor(
|
|||
startRedesignAddToPortfolioFlow()
|
||||
}
|
||||
|
||||
override fun onQuickActionClick(action: TokenActionsBSContentUM.Action) {
|
||||
analyticsEventHandler.send(eventBuilder.getTokenActionClick(actionUM = action))
|
||||
}
|
||||
|
||||
private fun <T> replayMutableSharedFlow() = MutableSharedFlow<T>(
|
||||
replay = 1,
|
||||
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||
|
|
@ -155,7 +153,7 @@ internal class AddToPortfolioModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun startRedesignAddToPortfolioFlow() {
|
||||
channelFlow<Unit> {
|
||||
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<TokenMarketInfo.Network>()
|
||||
val onChooseTokenBottomActionClick = Channel<Unit>()
|
||||
val onChooseTokenBottomActionClick = Channel<BottomAction>()
|
||||
val onQuickActionClick = Channel<Pair<TokenActionsBSContentUM.Action, Boolean>>()
|
||||
val onChangeNetworkClick = Channel<Unit>()
|
||||
val onChangePortfolioClick = Channel<Unit>()
|
||||
val onTokenAdded = Channel<CryptoCurrencyStatus>()
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.commonfeatures.impl.choosetoken
|
|||
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenAnalyticsPayload
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge
|
||||
|
|
@ -9,9 +10,9 @@ import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge.Sett
|
|||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.ChooseTokenPortfolioFullBlockUM
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.model.ChooseTokenModel
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.model.PortfolioFullBlockDelegate
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.model.PortfolioListBlockDelegate
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.model.ChooseTokenModel
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -32,7 +33,7 @@ internal class DefaultChooseTokenBridge @AssistedInject constructor(
|
|||
|
||||
private val onSearchQuery: Channel<SearchQuery> = Channel()
|
||||
override val searchQueryState: StateFlow<SearchQuery> = onSearchQuery.receiveAsFlow()
|
||||
.debounce(ChooseTokenModel.Companion.DEBOUNCE_SEARCH_DELAY)
|
||||
.debounce(ChooseTokenModel.DEBOUNCE_SEARCH_DELAY)
|
||||
.stateIn(modelScope, SharingStarted.Eagerly, initialValue = SearchQuery.Empty)
|
||||
|
||||
private val portfolioListBlockDelegate: PortfolioListBlockDelegate = portfolioListBlockDelegateFactory.create(
|
||||
|
|
@ -45,6 +46,7 @@ internal class DefaultChooseTokenBridge @AssistedInject constructor(
|
|||
modelScope = modelScope,
|
||||
searchQueryState = searchQueryState,
|
||||
portfolioListBlockDelegate = portfolioListBlockDelegate,
|
||||
featureSettings = settings,
|
||||
)
|
||||
|
||||
override val tokenFilter: MutableStateFlow<(AccountStatus, CryptoCurrencyStatus) -> Boolean>
|
||||
|
|
@ -53,6 +55,9 @@ internal class DefaultChooseTokenBridge @AssistedInject constructor(
|
|||
override val fullPortfolioBlock: StateFlow<ChooseTokenPortfolioFullBlockUM?>
|
||||
get() = portfolioFullBlockDelegate.fullPortfolioBlock
|
||||
|
||||
override val selectedWalletFlow: SharedFlow<UserWallet>
|
||||
get() = portfolioFullBlockDelegate.selectedWalletFlow
|
||||
|
||||
init {
|
||||
portfolioListBlockDelegate.onTokenChosen.receiveAsFlow()
|
||||
.onEach { chooseResult -> onCurrencyChosen(chooseResult) }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ internal class ChooseTokenListItemConverter(
|
|||
when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> accountStatus.toPortfolioItem(params)
|
||||
is AccountStatus.Payment -> accountStatus.createPaymentAccountItem(params.expandedAccounts)
|
||||
is AccountStatus.Virtual -> null
|
||||
}
|
||||
}
|
||||
.filter { portfolio -> portfolio.tokens.isNotEmpty() }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -41,6 +42,8 @@ internal class ChooseTokenModel @Inject constructor(
|
|||
screensSourcesName = bridge.analyticsPayload
|
||||
.filterIsInstance<ChooseTokenAnalyticsPayload.ScreensSources>()
|
||||
.firstOrNull()?.value.orEmpty(),
|
||||
selectedWalletFlow = bridge.selectedWalletFlow,
|
||||
shouldShowSingleCurrencyWallets = bridge.settings.isShowSingleCurrencyWallets,
|
||||
)
|
||||
|
||||
val bottomSheetNavigation get() = marketBlockDelegate.addToPortfolioSlot
|
||||
|
|
@ -78,18 +81,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 +92,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 +121,7 @@ internal class ChooseTokenModel @Inject constructor(
|
|||
|
||||
private fun getInitState() = ChooseTokenInitialUM(
|
||||
screenTitle = bridge.settings.title,
|
||||
isAppBarShown = bridge.settings.isAppBarShown,
|
||||
onCloseClick = ::onBackClicked,
|
||||
searchBar = getInitialSearchBar(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import com.tangem.domain.markets.TokenMarketListConfig
|
||||
|
|
@ -14,9 +17,9 @@ import com.tangem.domain.markets.toSerializableParam
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.AddToPortfolioRoute
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.MarketsListBatchFlowManager
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketState
|
||||
|
|
@ -25,11 +28,9 @@ import com.tangem.utils.Provider
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlin.collections.filter
|
||||
import kotlin.collections.map
|
||||
import kotlin.collections.orEmpty
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class MarketBlockDelegate @AssistedInject constructor(
|
||||
|
|
@ -37,9 +38,12 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val getUserWalletsUseCase: GetWalletsUseCase,
|
||||
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
@Assisted private val modelScope: CoroutineScope,
|
||||
@Assisted private val searchQueryState: StateFlow<SearchQuery>,
|
||||
@Assisted private val screensSourcesName: String,
|
||||
@Assisted private val selectedWalletFlow: SharedFlow<UserWallet>,
|
||||
@Assisted private val shouldShowSingleCurrencyWallets: Boolean,
|
||||
) {
|
||||
|
||||
private val visibleMarketItemIds = MutableStateFlow<List<CryptoCurrency.RawID>>(emptyList())
|
||||
|
|
@ -52,7 +56,7 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
analyticsParams = AddToPortfolioManager.AnalyticsParams(source = screensSourcesName),
|
||||
)
|
||||
|
||||
val marketsStateFlow: Flow<SwapMarketState> = searchQueryState
|
||||
private val baseMarketsStateFlow: Flow<SwapMarketState> = searchQueryState
|
||||
// Switch between default and search market flows
|
||||
.map { it.value.isEmpty() }
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -66,6 +70,24 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Market block constrained by the currently selected wallet:
|
||||
* - single-currency wallet: hidden entirely (`null`) - no market tokens can be added;
|
||||
* - single-currency-with-token wallet (e.g. NODL): items filtered to the wallet's network,
|
||||
* block hidden when nothing remains;
|
||||
* - multi-currency wallet: shown as is.
|
||||
*
|
||||
* When single-currency wallets aren't selectable here (e.g. swap), the wallet is always
|
||||
* multi-currency, so we skip the per-wallet logic entirely and return [baseMarketsStateFlow].
|
||||
*/
|
||||
val marketsStateFlow: Flow<SwapMarketState?> = if (!shouldShowSingleCurrencyWallets) {
|
||||
baseMarketsStateFlow
|
||||
} else {
|
||||
selectedWalletFlow
|
||||
.flatMapLatest(::marketsFlowForWallet)
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
private val defaultMarketsListManager by lazy {
|
||||
marketsListBatchFlowManagerFactory.create(
|
||||
batchFlowType = GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
|
||||
|
|
@ -181,6 +203,44 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun marketsFlowForWallet(wallet: UserWallet): Flow<SwapMarketState?> {
|
||||
if (wallet !is UserWallet.Cold) return baseMarketsStateFlow
|
||||
val resolver = wallet.scanResponse.cardTypesResolver
|
||||
return when {
|
||||
// Single-currency wallet can't hold market tokens - hide the whole block.
|
||||
resolver.isSingleWallet() -> flowOf(null)
|
||||
// Single-currency-with-token wallet (NODL) - keep only tokens available on the wallet's network(s).
|
||||
resolver.isSingleWalletWithToken() -> combine(
|
||||
baseMarketsStateFlow,
|
||||
singleAccountStatusListSupplier(wallet.walletId),
|
||||
) { state, accountStatusList ->
|
||||
filterStateByNetwork(state, accountStatusList.allowedNetworkIds())
|
||||
}
|
||||
// Multi-currency wallet - the common case, no filtering needed.
|
||||
else -> baseMarketsStateFlow
|
||||
}
|
||||
}
|
||||
|
||||
private fun AccountStatusList.allowedNetworkIds(): Set<String> =
|
||||
flattenCurrencies().mapTo(hashSetOf()) { it.currency.network.rawId }
|
||||
|
||||
private fun filterStateByNetwork(state: SwapMarketState, allowedNetworkIds: Set<String>): SwapMarketState? {
|
||||
if (state !is SwapMarketState.Content) return state
|
||||
if (allowedNetworkIds.isEmpty()) return null
|
||||
|
||||
val filteredItems = state.items.filter { item ->
|
||||
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
|
||||
?: searchMarketsListManager.getTokenMarketById(item.id)
|
||||
tokenMarket?.networks?.any { allowedNetworkIds.contains(it.networkId) } == true
|
||||
}.toImmutableList()
|
||||
|
||||
return if (filteredItems.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
state.copy(items = filteredItems, total = filteredItems.size)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToPortfolioItem(item: MarketsListItemUM) {
|
||||
val tokenMarket = defaultMarketsListManager.getTokenMarketById(item.id)
|
||||
?: searchMarketsListManager.getTokenMarketById(item.id) ?: return
|
||||
|
|
@ -218,6 +278,8 @@ internal class MarketBlockDelegate @AssistedInject constructor(
|
|||
searchQueryState: StateFlow<SearchQuery>,
|
||||
modelScope: CoroutineScope,
|
||||
screensSourcesName: String,
|
||||
selectedWalletFlow: SharedFlow<UserWallet>,
|
||||
shouldShowSingleCurrencyWallets: Boolean,
|
||||
): MarketBlockDelegate
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,11 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery.Companion.isSearchingState
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.model.ChooseTokenPortfolioFullBlockUM
|
||||
|
|
@ -35,9 +35,11 @@ internal class PortfolioFullBlockDelegate @AssistedInject constructor(
|
|||
@Assisted private val modelScope: CoroutineScope,
|
||||
@Assisted private val portfolioListBlockDelegate: PortfolioListBlockDelegate,
|
||||
@Assisted private val searchQueryState: StateFlow<SearchQuery>,
|
||||
@Assisted private val featureSettings: ChooseTokenBridge.Settings,
|
||||
) {
|
||||
|
||||
private val isSearchingState: Boolean get() = searchQueryState.isSearchingState
|
||||
private val isOnlyMultiCurrency: Boolean get() = !featureSettings.isShowSingleCurrencyWallets
|
||||
private val onWalletSelected = Channel<UserWalletId>(capacity = Channel.BUFFERED)
|
||||
|
||||
val selectedWalletFlow: SharedFlow<UserWallet> = onWalletSelected.receiveAsFlow()
|
||||
|
|
@ -51,9 +53,11 @@ internal class PortfolioFullBlockDelegate @AssistedInject constructor(
|
|||
|
||||
init {
|
||||
val globalSelectedWallet = selectedWalletUseCase.sync().getOrNull()
|
||||
val allWallets = getWalletsUseCase.invokeSync().filter { it.isMultiCurrency }
|
||||
val allWallets = getWalletsUseCase.invokeSync()
|
||||
.filter { !isOnlyMultiCurrency || it.isMultiCurrency }
|
||||
val firstSelectedWallet = when {
|
||||
globalSelectedWallet?.isMultiCurrency == true -> globalSelectedWallet
|
||||
globalSelectedWallet != null && (!isOnlyMultiCurrency || globalSelectedWallet.isMultiCurrency) ->
|
||||
globalSelectedWallet
|
||||
allWallets.isNotEmpty() -> allWallets.first()
|
||||
else -> null
|
||||
}
|
||||
|
|
@ -61,8 +65,10 @@ internal class PortfolioFullBlockDelegate @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun buildFlow() = flow {
|
||||
val walletsFlow = getWalletsUseCase.invokeAsMap()
|
||||
.map { wallets -> wallets.filterNot { (_, wallet) -> wallet.isLocked } }
|
||||
val walletsFlow = getWalletsUseCase.invokeAsMap(
|
||||
isOnlyMultiCurrency = isOnlyMultiCurrency,
|
||||
filterLocked = true,
|
||||
)
|
||||
val fullPortfolioBlockFlow = combine(
|
||||
flow = walletsFlow,
|
||||
flow2 = portfolioListBlockDelegate.portfolioList,
|
||||
|
|
@ -109,6 +115,7 @@ internal class PortfolioFullBlockDelegate @AssistedInject constructor(
|
|||
modelScope: CoroutineScope,
|
||||
portfolioListBlockDelegate: PortfolioListBlockDelegate,
|
||||
searchQueryState: StateFlow<SearchQuery>,
|
||||
featureSettings: ChooseTokenBridge.Settings,
|
||||
): PortfolioFullBlockDelegate
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
|||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.test.BuyTokenScreenTestTags
|
||||
|
|
@ -82,12 +83,20 @@ private val ChooseTokenFullUM.isEmptyState: Boolean
|
|||
internal fun ChooseTokenScreen(state: ChooseTokenFullUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(color = TangemTheme.colors.background.secondary)
|
||||
.background(
|
||||
color = if (LocalRedesignEnabled.current) {
|
||||
TangemTheme.colors2.surface.level2
|
||||
} else {
|
||||
TangemTheme.colors.background.secondary
|
||||
},
|
||||
)
|
||||
.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 +474,7 @@ private val wallets
|
|||
|
||||
private val initialUM = ChooseTokenInitialUM(
|
||||
screenTitle = stringReference("Choose token"),
|
||||
isAppBarShown = true,
|
||||
onCloseClick = {},
|
||||
searchBar = searchBar,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ internal data class ChooseTokenFullUM(
|
|||
|
||||
internal data class ChooseTokenInitialUM(
|
||||
val screenTitle: TextReference,
|
||||
val isAppBarShown: Boolean,
|
||||
val onCloseClick: () -> Unit,
|
||||
val searchBar: SearchBarUM,
|
||||
)
|
||||
|
|
@ -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<CryptoCurrencyData>,
|
||||
val callbacks: Callbacks,
|
||||
val bottomAction: BottomAction = BottomAction.Later,
|
||||
val bottomAction: Flow<BottomAction> = 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
|
||||
|
|
@ -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,10 @@ 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)
|
||||
},
|
||||
coroutineScope = modelScope,
|
||||
)
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<TokenReceiveConfig> = SlotNavigation()
|
||||
|
|
@ -55,15 +58,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 +78,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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
package com.tangem.features.commonfeatures.impl.choosetoken.model
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.card.WalletData
|
||||
import com.tangem.common.test.domain.card.MockScanResponseFactory
|
||||
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.card.configs.GenericCardConfig
|
||||
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
|
||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridgeInternal.SearchQuery
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.MarketsListBatchFlowManager
|
||||
import com.tangem.features.commonfeatures.impl.choosetoken.market.state.SwapMarketState
|
||||
import com.tangem.test.core.getEmittedValues
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class MarketBlockDelegateTest {
|
||||
|
||||
private val marketsListBatchFlowManagerFactory: MarketsListBatchFlowManager.Factory = mockk()
|
||||
private val excludedBlockchains: ExcludedBlockchains = mockk(relaxed = true)
|
||||
private val getUserWalletsUseCase: GetWalletsUseCase = mockk(relaxed = true)
|
||||
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory = mockk()
|
||||
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier = mockk()
|
||||
|
||||
private val defaultManager: MarketsListBatchFlowManager = mockk(relaxed = true)
|
||||
private val searchManager: MarketsListBatchFlowManager = mockk(relaxed = true)
|
||||
|
||||
private val searchQueryState = MutableStateFlow(SearchQuery.Empty)
|
||||
private val defaultUiItems = MutableStateFlow<ImmutableList<MarketsListItemUM>>(persistentListOf())
|
||||
|
||||
// Keyed by the raw id value: CryptoCurrency.RawID is a value class, unboxed to String at the JVM boundary.
|
||||
private val tokenMarketsByRawId = mutableMapOf<String, TokenMarket>()
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
clearMocks(
|
||||
marketsListBatchFlowManagerFactory,
|
||||
addToPortfolioManagerFactory,
|
||||
singleAccountStatusListSupplier,
|
||||
defaultManager,
|
||||
searchManager,
|
||||
)
|
||||
searchQueryState.value = SearchQuery.Empty
|
||||
defaultUiItems.value = persistentListOf()
|
||||
tokenMarketsByRawId.clear()
|
||||
|
||||
every {
|
||||
marketsListBatchFlowManagerFactory.create(
|
||||
GetMarketsTokenListFlowUseCase.BatchFlowType.Main,
|
||||
any(),
|
||||
any(),
|
||||
any()
|
||||
)
|
||||
} returns defaultManager
|
||||
every {
|
||||
marketsListBatchFlowManagerFactory.create(
|
||||
GetMarketsTokenListFlowUseCase.BatchFlowType.Search,
|
||||
any(),
|
||||
any(),
|
||||
any()
|
||||
)
|
||||
} returns searchManager
|
||||
every { addToPortfolioManagerFactory.create(any(), any(), any()) } returns mockk(relaxed = true)
|
||||
|
||||
every { defaultManager.uiItems } returns defaultUiItems
|
||||
every { defaultManager.isInInitialLoadingErrorState } returns MutableStateFlow(false)
|
||||
every { defaultManager.totalCount } returns MutableStateFlow(null)
|
||||
every { defaultManager.getTokenMarketById(any()) } answers { tokenMarketsByRawId[firstArg<String>()] }
|
||||
|
||||
every { searchManager.uiItems } returns MutableStateFlow(persistentListOf())
|
||||
every { searchManager.isInInitialLoadingErrorState } returns MutableStateFlow(false)
|
||||
every { searchManager.isSearchNotFoundState } returns MutableStateFlow(false)
|
||||
every { searchManager.totalCount } returns MutableStateFlow(null)
|
||||
every { searchManager.getTokenMarketById(any()) } returns null
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multi-currency wallet WHEN trending emitted THEN all items shown unchanged`() = runTest {
|
||||
// Arrange
|
||||
val item1 = marketItem("token-1")
|
||||
val item2 = marketItem("token-2")
|
||||
defaultUiItems.value = persistentListOf(item1, item2)
|
||||
val delegate = createDelegate(wallet = MockUserWalletFactory.create())
|
||||
|
||||
// Act
|
||||
val result = lastMarketState(delegate)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isInstanceOf(SwapMarketState.Content::class.java)
|
||||
assertThat((result as SwapMarketState.Content).items).containsExactly(item1, item2).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN single-currency wallet WHEN trending emitted THEN market block is hidden`() = runTest {
|
||||
// Arrange
|
||||
defaultUiItems.value = persistentListOf(marketItem("token-1"))
|
||||
val delegate = createDelegate(wallet = createSingleCurrencyWallet())
|
||||
|
||||
// Act
|
||||
val result = lastMarketState(delegate)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN single-currency wallets not shown WHEN trending emitted THEN base state returned without filtering`() =
|
||||
runTest {
|
||||
// Arrange
|
||||
val item1 = marketItem("token-1")
|
||||
defaultUiItems.value = persistentListOf(item1)
|
||||
// Single-currency wallet would normally hide the block, but the setting short-circuits the per-wallet logic.
|
||||
val delegate = createDelegate(wallet = createSingleCurrencyWallet(), showSingleCurrencyWallets = false)
|
||||
|
||||
// Act
|
||||
val result = lastMarketState(delegate)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isInstanceOf(SwapMarketState.Content::class.java)
|
||||
assertThat((result as SwapMarketState.Content).items).containsExactly(item1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN NODL wallet WHEN trending emitted THEN only items on wallet network are shown`() = runTest {
|
||||
// Arrange
|
||||
val nodlWallet = MockUserWalletFactory.createSingleWalletWithToken()
|
||||
val itemOnWalletNetwork = marketItem("token-stellar")
|
||||
val itemOnOtherNetwork = marketItem("token-eth")
|
||||
tokenMarketsByRawId["token-stellar"] = tokenMarket(STELLAR_NETWORK_ID)
|
||||
tokenMarketsByRawId["token-eth"] = tokenMarket(ETHEREUM_NETWORK_ID)
|
||||
defaultUiItems.value = persistentListOf(itemOnWalletNetwork, itemOnOtherNetwork)
|
||||
|
||||
every {
|
||||
singleAccountStatusListSupplier(nodlWallet.walletId)
|
||||
} returns flowOf(accountStatusList(STELLAR_NETWORK_ID))
|
||||
|
||||
// Act
|
||||
val result = lastMarketState(createDelegate(wallet = nodlWallet))
|
||||
|
||||
// Assert
|
||||
assertThat(result).isInstanceOf(SwapMarketState.Content::class.java)
|
||||
assertThat((result as SwapMarketState.Content).items).containsExactly(itemOnWalletNetwork)
|
||||
assertThat(result.total).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN NODL wallet WHEN no trending tokens on wallet network THEN market block is hidden`() = runTest {
|
||||
// Arrange
|
||||
val nodlWallet = MockUserWalletFactory.createSingleWalletWithToken()
|
||||
val itemOnOtherNetwork = marketItem("token-eth")
|
||||
tokenMarketsByRawId["token-eth"] = tokenMarket(ETHEREUM_NETWORK_ID)
|
||||
defaultUiItems.value = persistentListOf(itemOnOtherNetwork)
|
||||
|
||||
every {
|
||||
singleAccountStatusListSupplier(nodlWallet.walletId)
|
||||
} returns flowOf(accountStatusList(STELLAR_NETWORK_ID))
|
||||
|
||||
// Act
|
||||
val result = lastMarketState(createDelegate(wallet = nodlWallet))
|
||||
|
||||
// Assert
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
// region Helpers
|
||||
|
||||
private fun TestScope.lastMarketState(delegate: MarketBlockDelegate): SwapMarketState? {
|
||||
val emittedValues = getEmittedValues(delegate.marketsStateFlow)
|
||||
advanceUntilIdle()
|
||||
return emittedValues.last()
|
||||
}
|
||||
|
||||
private fun TestScope.createDelegate(
|
||||
wallet: UserWallet,
|
||||
showSingleCurrencyWallets: Boolean = true,
|
||||
): MarketBlockDelegate {
|
||||
val selectedWalletFlow = MutableSharedFlow<UserWallet>(replay = 1)
|
||||
selectedWalletFlow.tryEmit(wallet)
|
||||
return MarketBlockDelegate(
|
||||
marketsListBatchFlowManagerFactory = marketsListBatchFlowManagerFactory,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
getUserWalletsUseCase = getUserWalletsUseCase,
|
||||
addToPortfolioManagerFactory = addToPortfolioManagerFactory,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
modelScope = backgroundScope,
|
||||
searchQueryState = searchQueryState,
|
||||
screensSourcesName = "test",
|
||||
selectedWalletFlow = selectedWalletFlow,
|
||||
shouldShowSingleCurrencyWallets = showSingleCurrencyWallets,
|
||||
)
|
||||
}
|
||||
|
||||
private fun marketItem(id: String): MarketsListItemUM = mockk {
|
||||
every { this@mockk.id } returns CryptoCurrency.RawID(id)
|
||||
}
|
||||
|
||||
private fun tokenMarket(vararg networkIds: String): TokenMarket = mockk {
|
||||
every { networks } returns networkIds.map { networkId ->
|
||||
TokenMarket.Network(networkId = networkId, contractAddress = null, decimalCount = null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun accountStatusList(vararg networkIds: String): AccountStatusList = mockk {
|
||||
every { flattenCurrencies() } returns networkIds.map { networkId ->
|
||||
mockk<CryptoCurrencyStatus> {
|
||||
every { currency.network.rawId } returns networkId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSingleCurrencyWallet(): UserWallet.Cold = UserWallet.Cold(
|
||||
name = "Single",
|
||||
walletId = UserWalletId("022"),
|
||||
cardsInWallet = emptySet(),
|
||||
isMultiCurrency = false,
|
||||
scanResponse = MockScanResponseFactory.create(
|
||||
cardConfig = GenericCardConfig(maxWalletCount = 2),
|
||||
derivedKeys = emptyMap(),
|
||||
).copy(
|
||||
productType = ProductType.Note,
|
||||
walletData = WalletData(blockchain = "XLM", token = null),
|
||||
),
|
||||
hasBackupError = false,
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
private companion object {
|
||||
const val STELLAR_NETWORK_ID = "stellar"
|
||||
const val ETHEREUM_NETWORK_ID = "ethereum"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue