Updated on 2026-08-14

This commit is contained in:
Tangem 2026-06-23 13:17:08 +04:00
parent 428f1076f0
commit 850c904233
9 changed files with 399 additions and 74 deletions

View file

@ -51,7 +51,10 @@ inline fun <reified T : StoryConfig> StoriesContainer(
) {
var watchedCounter by remember { mutableIntStateOf(1) }
var isPressed by remember { mutableStateOf(value = false) }
val storyState by remember(config) {
// Key on content (stories + repeatability), not the whole config object: an unrelated change
// to the config instance (e.g. a caller folding a changing flag into it) must not recreate the
// state machine and rewind stories to the first page.
val storyState by remember(config.stories, config.isRestartable) {
mutableStateOf(
StoriesStepStateMachine(
stories = config.stories,
@ -59,7 +62,9 @@ inline fun <reified T : StoryConfig> StoriesContainer(
),
)
}
BackHandler(onBack = { config.onClose(watchedCounter) })
if (config.isCloseButtonVisible) {
BackHandler(onBack = { config.onClose(watchedCounter) })
}
val isPaused = isPressed || isPauseStories
@ -90,22 +95,24 @@ inline fun <reified T : StoryConfig> StoriesContainer(
paused = isPaused,
onStepFinish = onNextClick,
)
Icon(
painter = rememberVectorPainter(
image = ImageVector.vectorResource(R.drawable.ic_close_24),
),
tint = TangemTheme.colors.icon.constant,
contentDescription = null,
modifier = Modifier
.align(Alignment.End)
.padding(top = 14.dp, end = 16.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = LocalIndication.current,
onClick = { config.onClose(watchedCounter) },
)
.testTag(SwapStoriesScreenTestTags.CLOSE_BUTTON),
)
if (config.isCloseButtonVisible) {
Icon(
painter = rememberVectorPainter(
image = ImageVector.vectorResource(R.drawable.ic_close_24),
),
tint = TangemTheme.colors.icon.constant,
contentDescription = null,
modifier = Modifier
.align(Alignment.End)
.padding(top = 14.dp, end = 16.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = LocalIndication.current,
onClick = { config.onClose(watchedCounter) },
)
.testTag(SwapStoriesScreenTestTags.CLOSE_BUTTON),
)
}
}
}
}

View file

@ -5,13 +5,18 @@ import kotlinx.collections.immutable.ImmutableList
/**
* Config for stories component
*
* @property stories configuration list
* @property stories configuration list
* @property isRestartable indicates than stories progressions starts
* @property isCloseButtonVisible whether the top-right close button (and back handler) is shown.
* Set `false` for non-closable stories (e.g. a root intro screen); [onClose] is then irrelevant.
* @property onClose invoked with the number of watched stories when the user closes them.
* Only meaningful when [isCloseButtonVisible] is `true`; defaults to a no-op for non-closable stories.
*/
interface StoriesContentConfig<T : StoryConfig> {
val stories: ImmutableList<T>
val isRestartable: Boolean
val onClose: (Int) -> Unit
val isCloseButtonVisible: Boolean get() = true
val onClose: (Int) -> Unit get() = {}
}
interface StoryConfig {

View file

@ -69,4 +69,7 @@ dependencies {
/** DI */
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)
}
/** Tests */
testImplementation(projects.test.core)
}

View file

@ -3,8 +3,6 @@ package com.tangem.features.home.impl.model
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRoute.ManageTokens.Source
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
@ -16,11 +14,9 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.message.dialog.Dialogs
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.analytics.IntroductionProcess
import com.tangem.domain.card.analytics.Shop
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
@ -30,11 +26,11 @@ import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase
import com.tangem.features.home.api.HomeComponent
import com.tangem.features.home.api.HomeFeatureToggles
import com.tangem.features.home.impl.ui.state.HomeStoriesConfig
import com.tangem.features.home.impl.ui.state.HomeUM
import com.tangem.features.home.impl.ui.state.Stories
import com.tangem.features.home.impl.ui.state.getRestrictedStories
@ -60,12 +56,9 @@ internal class HomeModel @Inject constructor(
private val settingsRepository: SettingsRepository,
private val analyticsEventHandler: AnalyticsEventHandler,
private val router: Router,
private val appRouter: AppRouter,
private val getUserCountryUseCase: GetUserCountryUseCase,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val saveWalletUseCase: SaveWalletUseCase,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val urlOpener: UrlOpener,
private val userWalletsListRepository: UserWalletsListRepository,
private val shouldShowMobileWalletPromoUseCase: ShouldShowMobileWalletPromoUseCase,
private val homeFeatureToggles: HomeFeatureToggles,
@ -76,18 +69,8 @@ internal class HomeModel @Inject constructor(
val params = paramsContainer.require<HomeComponent.Params>()
private val _uiState = MutableStateFlow(
HomeUM(
scanInProgress = false,
isStoriesContainerEnabled = homeFeatureToggles.isStoriesContainerEnabled,
stories = getRestrictedStories().toImmutableList(),
onShopClick = ::onShopClick,
onSearchTokensClick = ::onSearchTokensClick,
onGetStartedClick = ::onGetStartedClick,
),
)
val uiState = _uiState.asStateFlow()
val uiState: StateFlow<HomeUM>
field = MutableStateFlow(createInitialState())
init {
analyticsEventHandler.send(IntroductionProcess.ScreenOpened())
@ -99,6 +82,17 @@ internal class HomeModel @Inject constructor(
}
}
private fun createInitialState(): HomeUM {
val initialStories = getRestrictedStories().toImmutableList()
return HomeUM(
scanInProgress = false,
isStoriesContainerEnabled = homeFeatureToggles.isStoriesContainerEnabled,
stories = initialStories,
storiesConfig = HomeStoriesConfig(stories = initialStories),
onGetStartedClick = ::onGetStartedClick,
)
}
private fun observeUserCountryChanges() {
getUserCountryUseCase.invoke()
.distinctUntilChanged()
@ -117,35 +111,21 @@ internal class HomeModel @Inject constructor(
} else {
Stories.entries
}
.toImmutableList()
_uiState.update {
it.copy(stories = stories.toImmutableList())
uiState.update {
it.copy(stories = stories, storiesConfig = HomeStoriesConfig(stories = stories))
}
}
private fun onShopClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonBuyCards())
analyticsEventHandler.send(Shop.ScreenOpened())
modelScope.launch {
generateBuyTangemCardLinkUseCase.invoke(null).let { urlOpener.openUrl(it) }
}
}
private fun onSearchTokensClick() {
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
router.push(AppRoute.ManageTokens(Source.STORIES))
}
private fun onGetStartedClick() {
debouncer.debounce(modelScope) {
modelScope.launch {
val mode = if (shouldShowMobileWalletPromoUseCase()) {
AppRoute.CreateWalletStart.Mode.HotWallet
} else {
AppRoute.CreateWalletStart.Mode.ColdWallet
}
router.push(AppRoute.CreateWalletStart(mode = mode))
val mode = if (shouldShowMobileWalletPromoUseCase()) {
AppRoute.CreateWalletStart.Mode.HotWallet
} else {
AppRoute.CreateWalletStart.Mode.ColdWallet
}
router.push(AppRoute.CreateWalletStart(mode = mode))
}
}
@ -201,13 +181,13 @@ internal class HomeModel @Inject constructor(
setLoading(false)
when (error) {
is SaveWalletError.DataError -> TangemLogger.e("Unable to save user wallet: $error")
is SaveWalletError.WalletAlreadySaved -> appRouter.replaceAll(AppRoute.Wallet)
is SaveWalletError.WalletAlreadySaved -> router.replaceAll(AppRoute.Wallet)
}
},
ifRight = {
setLoading(false)
sendSignedInCardAnalyticsEvent(scanResponse, userWallet.isImported)
appRouter.replaceAll(AppRoute.Wallet)
router.replaceAll(AppRoute.Wallet)
},
)
}
@ -224,7 +204,7 @@ internal class HomeModel @Inject constructor(
}
private fun setLoading(isLoading: Boolean) {
_uiState.update { it.copy(scanInProgress = isLoading) }
uiState.update { it.copy(scanInProgress = isLoading) }
}
private fun handleScanError(error: TangemError) {

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.SystemBarsIconsDisposable
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.utils.ChangeRootBackgroundColorEffect
import com.tangem.features.home.impl.ui.compose.HomeStoriesScreen
import com.tangem.features.home.impl.ui.compose.StoriesScreenV2
import com.tangem.features.home.impl.ui.state.HomeUM
@ -12,11 +13,18 @@ import com.tangem.features.home.impl.ui.state.HomeUM
internal fun Home(state: HomeUM, modifier: Modifier = Modifier) {
SystemBarsIconsDisposable(darkIcons = false)
StoriesScreenV2(
modifier = modifier,
state = state,
onGetStartedClick = state.onGetStartedClick,
)
if (state.isStoriesContainerEnabled) {
HomeStoriesScreen(
modifier = modifier,
state = state,
)
} else {
StoriesScreenV2(
modifier = modifier,
state = state,
onGetStartedClick = state.onGetStartedClick,
)
}
ChangeRootBackgroundColorEffect(TangemColorPalette.Black)
}

View file

@ -0,0 +1,102 @@
package com.tangem.features.home.impl.ui.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.R
import com.tangem.core.ui.components.stories.StoriesContainer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.StoriesScreenTestTags
import com.tangem.features.home.impl.ui.compose.content.FirstStoriesContent
import com.tangem.features.home.impl.ui.compose.content.StoriesCurrencies
import com.tangem.features.home.impl.ui.compose.content.StoriesRevolutionaryWallet
import com.tangem.features.home.impl.ui.compose.content.StoriesUltraSecureBackup
import com.tangem.features.home.impl.ui.compose.content.StoriesWalletForEveryone
import com.tangem.features.home.impl.ui.compose.content.StoriesWeb3
import com.tangem.features.home.impl.ui.compose.views.HomeButtonsV2
import com.tangem.features.home.impl.ui.state.HomeUM
import com.tangem.features.home.impl.ui.state.Stories
private const val BACKGROUND_COLOR = 0xFF010101L
/**
* Home stories built on the shared [StoriesContainer].
* The container provides the progress bar, tap/hold navigation and pause; this screen supplies the
* per-story content, the Tangem logo and the persistent "Get Started" button.
*/
@Composable
internal fun HomeStoriesScreen(state: HomeUM, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxSize()
.background(Color(BACKGROUND_COLOR))
.testTag(StoriesScreenTestTags.SCREEN_CONTAINER),
) {
StoriesContainer(
modifier = Modifier.fillMaxSize(),
config = state.storiesConfig,
isPauseStories = state.scanInProgress,
) { story, isPaused ->
Column(
modifier = Modifier
.statusBarsPadding()
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Image(
painter = painterResource(id = R.drawable.ic_tangem_logo),
contentDescription = null,
contentScale = ContentScale.FillHeight,
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing16,
top = TangemTheme.dimens.spacing16,
)
.height(TangemTheme.dimens.size18)
.align(Alignment.Start),
)
when (story) {
Stories.TangemIntro -> FirstStoriesContent(isPaused = isPaused, duration = story.duration)
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
isPaused = isPaused,
stepDuration = story.duration,
)
Stories.Currencies -> StoriesCurrencies(isPaused, story.duration)
Stories.Web3 -> StoriesWeb3(isPaused, story.duration)
Stories.WalletForEveryone -> StoriesWalletForEveryone(story.duration)
}
}
}
Column(
modifier = Modifier
.navigationBarsPadding()
.padding(bottom = TangemTheme.dimens.spacing16)
.padding(horizontal = TangemTheme.dimens.spacing16)
.align(Alignment.BottomCenter)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
HomeButtonsV2(
modifier = Modifier.fillMaxWidth(),
onGetStartedClick = state.onGetStartedClick,
)
}
}
}

View file

@ -27,7 +27,9 @@ import com.tangem.features.home.impl.ui.state.Stories
import kotlin.math.max
import com.tangem.core.ui.R
import com.tangem.features.home.impl.ui.state.HomeUM
import com.tangem.utils.annotations.RemoveWithToggle
@RemoveWithToggle("AND_15901_STORIES_CONTAINER_ENABLED")
@Composable
internal fun StoriesScreenV2(state: HomeUM, onGetStartedClick: () -> Unit, modifier: Modifier = Modifier) {
var currentStory by remember { mutableStateOf(state.firstStory) }

View file

@ -1,13 +1,14 @@
package com.tangem.features.home.impl.ui.state
import com.tangem.core.ui.components.stories.model.StoriesContentConfig
import com.tangem.core.ui.components.stories.model.StoryConfig
import kotlinx.collections.immutable.ImmutableList
data class HomeUM(
val scanInProgress: Boolean,
val isStoriesContainerEnabled: Boolean,
val stories: ImmutableList<Stories>,
val onShopClick: () -> Unit,
val onSearchTokensClick: () -> Unit,
val storiesConfig: HomeStoriesConfig,
val onGetStartedClick: () -> Unit,
) {
val firstStory: Stories get() = stories[0]
@ -15,7 +16,17 @@ data class HomeUM(
fun stepOf(story: Stories): Int = stories.indexOf(story)
}
enum class Stories(val duration: Int = 6000) {
/**
* Config for the redesigned Home stories ([StoriesContainer]). The Home intro loops forever and is
* not closable, so [isCloseButtonVisible] is `false` and [onClose] keeps its no-op default.
*/
data class HomeStoriesConfig(
override val stories: ImmutableList<Stories>,
override val isRestartable: Boolean = true,
override val isCloseButtonVisible: Boolean = false,
) : StoriesContentConfig<Stories>
enum class Stories(override val duration: Int = 6000) : StoryConfig {
TangemIntro,
RevolutionaryWallet,
UltraSecureBackup,

View file

@ -0,0 +1,207 @@
package com.tangem.features.home.impl.model
import arrow.core.right
import com.google.common.truth.Truth.assertThat
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.model.MutableParamsContainer
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.feature.referral.domain.ShouldShowMobileWalletPromoUseCase
import com.tangem.features.home.api.HomeComponent
import com.tangem.features.home.api.HomeFeatureToggles
import com.tangem.features.home.impl.ui.state.Stories
import com.tangem.features.home.impl.ui.state.getRestrictedStories
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
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
@OptIn(ExperimentalCoroutinesApi::class)
internal class HomeModelTest {
private val scanCardProcessor: ScanCardProcessor = mockk()
private val cardSdkConfigRepository: CardSdkConfigRepository = mockk(relaxed = true)
private val settingsRepository: SettingsRepository = mockk()
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
private val router: Router = mockk(relaxed = true)
private val getUserCountryUseCase: GetUserCountryUseCase = mockk()
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory = mockk(relaxed = true)
private val saveWalletUseCase: SaveWalletUseCase = mockk(relaxed = true)
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxed = true)
private val shouldShowMobileWalletPromoUseCase: ShouldShowMobileWalletPromoUseCase = mockk(relaxed = true)
private val homeFeatureToggles: HomeFeatureToggles = mockk()
private val uiMessageSender: UiMessageSender = mockk(relaxed = true)
private val progressSlot = slot<suspend (Boolean) -> Unit>()
@BeforeEach
fun setUp() {
every { homeFeatureToggles.isStoriesContainerEnabled } returns false
every { getUserCountryUseCase.invoke() } returns emptyFlow()
coEvery { settingsRepository.shouldSaveAccessCodes() } returns false
coEvery {
scanCardProcessor.scan(
analyticsSource = any(),
shouldCheckIsAlreadyActivated = any(),
cardId = any(),
onProgressStateChange = capture(progressSlot),
onWalletNotCreated = any(),
onCancel = any(),
onFailure = any(),
onSuccess = any(),
)
} just Runs
}
@Test
fun `GIVEN toggle enabled WHEN model created THEN isStoriesContainerEnabled is true`() = runTest {
// Arrange
every { homeFeatureToggles.isStoriesContainerEnabled } returns true
// Act
val model = createModel(testScope = this)
advanceUntilIdle()
// Assert
assertThat(model.uiState.value.isStoriesContainerEnabled).isTrue()
model.onDestroy()
}
@Test
fun `GIVEN toggle disabled WHEN model created THEN isStoriesContainerEnabled is false`() = runTest {
// Arrange
every { homeFeatureToggles.isStoriesContainerEnabled } returns false
// Act
val model = createModel(testScope = this)
advanceUntilIdle()
// Assert
assertThat(model.uiState.value.isStoriesContainerEnabled).isFalse()
model.onDestroy()
}
@Test
fun `GIVEN model created WHEN no country emitted THEN storiesConfig is non-closable looping and in sync`() =
runTest {
// Act
val model = createModel(testScope = this)
advanceUntilIdle()
// Assert
val state = model.uiState.value
assertThat(state.storiesConfig.isRestartable).isTrue()
assertThat(state.storiesConfig.isCloseButtonVisible).isFalse()
assertThat(state.storiesConfig.stories).isEqualTo(state.stories)
assertThat(state.stories).containsExactlyElementsIn(getRestrictedStories()).inOrder()
model.onDestroy()
}
@Test
fun `GIVEN FCA-restricted country WHEN model created THEN Currencies excluded and config in sync`() = runTest {
// Arrange
every { getUserCountryUseCase.invoke() } returns flowOf(UserCountry.Other(code = "GB").right())
// Act
val model = createModel(testScope = this)
advanceUntilIdle()
// Assert
val state = model.uiState.value
assertThat(state.stories).containsExactlyElementsIn(getRestrictedStories()).inOrder()
assertThat(state.stories).doesNotContain(Stories.Currencies)
assertThat(state.storiesConfig.stories).isEqualTo(state.stories)
model.onDestroy()
}
@Test
fun `GIVEN non-restricted country WHEN model created THEN all stories shown and config in sync`() = runTest {
// Arrange
every { getUserCountryUseCase.invoke() } returns flowOf(UserCountry.Russia.right())
// Act
val model = createModel(testScope = this)
advanceUntilIdle()
// Assert
val state = model.uiState.value
assertThat(state.stories).containsExactlyElementsIn(Stories.entries).inOrder()
assertThat(state.storiesConfig.stories).isEqualTo(state.stories)
model.onDestroy()
}
@Test
fun `GIVEN scan in progress WHEN loading toggles THEN storiesConfig instance is not replaced`() = runTest {
// Arrange
val model = createModel(testScope = this, launchMode = InitScreenLaunchMode.WithCardScan)
advanceUntilIdle()
val initialConfig = model.uiState.value.storiesConfig
// Act + Assert — loading on
progressSlot.captured.invoke(true)
advanceUntilIdle()
assertThat(model.uiState.value.scanInProgress).isTrue()
assertThat(model.uiState.value.storiesConfig).isSameInstanceAs(initialConfig)
// Act + Assert — loading off
progressSlot.captured.invoke(false)
advanceUntilIdle()
assertThat(model.uiState.value.scanInProgress).isFalse()
assertThat(model.uiState.value.storiesConfig).isSameInstanceAs(initialConfig)
model.onDestroy()
}
private fun createModel(
testScope: TestScope,
launchMode: InitScreenLaunchMode = InitScreenLaunchMode.Standard,
paramsContainer: ParamsContainer = MutableParamsContainer(
value = HomeComponent.Params(launchMode = launchMode),
),
): HomeModel {
return HomeModel(
paramsContainer = paramsContainer,
dispatchers = testScope.createTestingCoroutineDispatcherProvider(),
scanCardProcessor = scanCardProcessor,
cardSdkConfigRepository = cardSdkConfigRepository,
settingsRepository = settingsRepository,
analyticsEventHandler = analyticsEventHandler,
router = router,
getUserCountryUseCase = getUserCountryUseCase,
coldUserWalletBuilderFactory = coldUserWalletBuilderFactory,
saveWalletUseCase = saveWalletUseCase,
userWalletsListRepository = userWalletsListRepository,
shouldShowMobileWalletPromoUseCase = shouldShowMobileWalletPromoUseCase,
homeFeatureToggles = homeFeatureToggles,
uiMessageSender = uiMessageSender,
)
}
private fun TestScope.createTestingCoroutineDispatcherProvider(): TestingCoroutineDispatcherProvider {
val testDispatcher = StandardTestDispatcher(testScheduler)
return TestingCoroutineDispatcherProvider(
main = testDispatcher,
mainImmediate = testDispatcher,
io = testDispatcher,
default = testDispatcher,
single = testDispatcher,
)
}
}