Updated on 2026-08-14
This commit is contained in:
commit
858a91d095
22 changed files with 387 additions and 103 deletions
|
|
@ -25,7 +25,6 @@ import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||||
import com.tangem.features.managetokens.component.ManageTokensMode
|
import com.tangem.features.managetokens.component.ManageTokensMode
|
||||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||||
import com.tangem.features.nft.component.NFTComponent
|
import com.tangem.features.nft.component.NFTComponent
|
||||||
import com.tangem.features.onboarding.v2.addresssync.AddressSyncComponent
|
|
||||||
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
|
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
|
||||||
import com.tangem.features.onramp.component.*
|
import com.tangem.features.onramp.component.*
|
||||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||||
|
|
@ -112,7 +111,6 @@ internal class ChildFactory @Inject constructor(
|
||||||
private val kycComponentFactory: KycComponent.Factory,
|
private val kycComponentFactory: KycComponent.Factory,
|
||||||
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
||||||
private val feedEntryComponentFactory: FeedEntryComponent.Factory,
|
private val feedEntryComponentFactory: FeedEntryComponent.Factory,
|
||||||
private val addressSyncComponentFactory: AddressSyncComponent.Factory,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||||
|
|
@ -262,6 +260,8 @@ internal class ChildFactory @Inject constructor(
|
||||||
OnboardingEntryComponent.Mode.ContinueFinalize
|
OnboardingEntryComponent.Mode.ContinueFinalize
|
||||||
is AppRoute.Onboarding.Mode.UpgradeHotWallet ->
|
is AppRoute.Onboarding.Mode.UpgradeHotWallet ->
|
||||||
OnboardingEntryComponent.Mode.UpgradeHotWallet(mode.userWalletId)
|
OnboardingEntryComponent.Mode.UpgradeHotWallet(mode.userWalletId)
|
||||||
|
is AppRoute.Onboarding.Mode.AddressSync ->
|
||||||
|
OnboardingEntryComponent.Mode.AddressSync
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
componentFactory = onboardingEntryComponentFactory,
|
componentFactory = onboardingEntryComponentFactory,
|
||||||
|
|
@ -694,13 +694,6 @@ internal class ChildFactory @Inject constructor(
|
||||||
componentFactory = feedEntryComponentFactory,
|
componentFactory = feedEntryComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AppRoute.AddressSync -> {
|
|
||||||
createComponentChild(
|
|
||||||
context = context,
|
|
||||||
params = AddressSyncComponent.Params(data = Unit),
|
|
||||||
componentFactory = addressSyncComponentFactory,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,7 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
data object RecreateWalletTwin : Mode() // reset twins
|
data object RecreateWalletTwin : Mode() // reset twins
|
||||||
data object ContinueFinalize : Mode() // continue finalize process (unfinished backup dialog)
|
data object ContinueFinalize : Mode() // continue finalize process (unfinished backup dialog)
|
||||||
data class UpgradeHotWallet(val userWalletId: UserWalletId) : Mode() // upgrade hot wallet
|
data class UpgradeHotWallet(val userWalletId: UserWalletId) : Mode() // upgrade hot wallet
|
||||||
|
data object AddressSync : Mode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,7 +485,4 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
|
data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data object AddressSync : AppRoute(path = "/address_sync")
|
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +237,10 @@ internal class CreateWalletStartModel @Inject constructor(
|
||||||
ifRight = {
|
ifRight = {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
val route = if (onboardingV2FeatureToggles.isAddressSyncEnabled) {
|
val route = if (onboardingV2FeatureToggles.isAddressSyncEnabled) {
|
||||||
AppRoute.AddressSync
|
AppRoute.Onboarding(
|
||||||
|
scanResponse = scanResponse,
|
||||||
|
mode = AppRoute.Onboarding.Mode.AddressSync,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
AppRoute.Wallet
|
AppRoute.Wallet
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -531,7 +531,17 @@ internal class CreateWalletStartModelTest {
|
||||||
model.uiState.value.onScanClick.invoke()
|
model.uiState.value.onScanClick.invoke()
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
|
|
||||||
verify { appRouter.replaceAll(routes = arrayOf(AppRoute.AddressSync), onComplete = any()) }
|
verify {
|
||||||
|
appRouter.replaceAll(
|
||||||
|
routes = arrayOf(
|
||||||
|
AppRoute.Onboarding(
|
||||||
|
scanResponse = testScanResponse,
|
||||||
|
mode = AppRoute.Onboarding.Mode.AddressSync,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
onComplete = any()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -573,9 +583,9 @@ internal class CreateWalletStartModelTest {
|
||||||
)
|
)
|
||||||
assert(state.imageResId == R.drawable.img_hardware_wallet)
|
assert(state.imageResId == R.drawable.img_hardware_wallet)
|
||||||
assert(state.shouldShowScanSecondaryButton)
|
assert(state.shouldShowScanSecondaryButton)
|
||||||
assert(state.primaryButtonText == resourceReference(R.string.details_buy_wallet),)
|
assert(state.primaryButtonText == resourceReference(R.string.details_buy_wallet))
|
||||||
assert(state.otherMethodTitle == resourceReference(R.string.welcome_create_wallet_mobile_title),)
|
assert(state.otherMethodTitle == resourceReference(R.string.welcome_create_wallet_mobile_title))
|
||||||
assert(state.otherMethodDescription == null,)
|
assert(state.otherMethodDescription == null)
|
||||||
assert(state.isScanInProgress.not())
|
assert(state.isScanInProgress.not())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ interface OnboardingEntryComponent : ComposableContentComponent {
|
||||||
data object RecreateWalletTwin : Mode()
|
data object RecreateWalletTwin : Mode()
|
||||||
data object ContinueFinalize : Mode()
|
data object ContinueFinalize : Mode()
|
||||||
data class UpgradeHotWallet(val userWalletId: UserWalletId) : Mode()
|
data class UpgradeHotWallet(val userWalletId: UserWalletId) : Mode()
|
||||||
|
data object AddressSync : Mode()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, OnboardingEntryComponent>
|
interface Factory : ComponentFactory<Params, OnboardingEntryComponent>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,16 @@ android {
|
||||||
namespace = "com.tangem.features.onboarding.v2.impl"
|
namespace = "com.tangem.features.onboarding.v2.impl"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<Test>().configureEach {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
/** Api */
|
/** Api */
|
||||||
implementation(projects.features.onboardingV2.api)
|
implementation(projects.features.onboardingV2.api)
|
||||||
implementation(projects.features.manageTokens.api)
|
implementation(projects.features.manageTokens.api)
|
||||||
implementation(projects.features.biometry.api)
|
implementation(projects.features.biometry.api)
|
||||||
|
implementation(projects.features.pushNotifications.api)
|
||||||
implementation(projects.features.hotWallet.api)
|
implementation(projects.features.hotWallet.api)
|
||||||
implementation(projects.features.tokenRecieve.api)
|
implementation(projects.features.tokenRecieve.api)
|
||||||
|
|
||||||
|
|
@ -87,4 +92,11 @@ dependencies {
|
||||||
/** DI */
|
/** DI */
|
||||||
implementation(deps.hilt.android)
|
implementation(deps.hilt.android)
|
||||||
kapt(deps.hilt.kapt)
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
|
/** Test */
|
||||||
|
testImplementation(deps.test.junit5)
|
||||||
|
testRuntimeOnly(deps.test.junit5.engine)
|
||||||
|
testImplementation(deps.test.mockk)
|
||||||
|
testImplementation(deps.test.truth)
|
||||||
|
testImplementation(deps.test.coroutine)
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
package com.tangem.features.onboarding.v2.addresssync
|
package com.tangem.features.onboarding.v2.addresssync
|
||||||
|
|
||||||
import com.tangem.core.decompose.factory.ComponentFactory
|
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
|
||||||
interface AddressSyncComponent : ComposableContentComponent {
|
interface AddressSyncComponent : ComposableContentComponent
|
||||||
|
|
||||||
data class Params(
|
|
||||||
val data: Any, // TODO("Will be implemented during [REDACTED_TASK_KEY]")
|
|
||||||
)
|
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, AddressSyncComponent>
|
|
||||||
}
|
|
||||||
|
|
@ -2,38 +2,138 @@ package com.tangem.features.onboarding.v2.addresssync
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import com.arkivanov.decompose.DelicateDecomposeApi
|
||||||
|
import com.arkivanov.decompose.extensions.compose.stack.Children
|
||||||
|
import com.arkivanov.decompose.router.stack.ChildStack
|
||||||
|
import com.arkivanov.decompose.router.stack.childStack
|
||||||
|
import com.arkivanov.decompose.value.Value
|
||||||
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.core.decompose.context.childByContext
|
||||||
import com.tangem.core.decompose.model.getOrCreateModel
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
|
import com.tangem.features.biometry.AskBiometryComponent
|
||||||
|
import com.tangem.features.onboarding.v2.addresssync.model.AddressSyncIntent
|
||||||
import com.tangem.features.onboarding.v2.addresssync.model.AddressSyncModel
|
import com.tangem.features.onboarding.v2.addresssync.model.AddressSyncModel
|
||||||
|
import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
|
||||||
import com.tangem.features.onboarding.v2.addresssync.ui.AddressSyncContent
|
import com.tangem.features.onboarding.v2.addresssync.ui.AddressSyncContent
|
||||||
import dagger.assisted.Assisted
|
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||||
import dagger.assisted.AssistedFactory
|
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||||
import dagger.assisted.AssistedInject
|
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||||
|
|
||||||
internal class DefaultAddressSyncComponent @AssistedInject constructor(
|
@OptIn(DelicateDecomposeApi::class)
|
||||||
@Assisted appComponentContext: AppComponentContext,
|
internal class DefaultAddressSyncComponent(
|
||||||
@Assisted params: AddressSyncComponent.Params,
|
appComponentContext: AppComponentContext,
|
||||||
|
private val askBiometryComponentFactory: AskBiometryComponent.Factory,
|
||||||
|
private val pushNotificationsComponentFactory: PushNotificationsComponent.Factory,
|
||||||
) : AppComponentContext by appComponentContext, AddressSyncComponent {
|
) : AppComponentContext by appComponentContext, AddressSyncComponent {
|
||||||
|
|
||||||
private val model: AddressSyncModel = getOrCreateModel(params)
|
private val model: AddressSyncModel = getOrCreateModel()
|
||||||
|
|
||||||
|
private val childStack: Value<ChildStack<AddressSyncStep, ComposableContentComponent>> =
|
||||||
|
childStack(
|
||||||
|
key = "innerStack",
|
||||||
|
source = model.stackNavigation,
|
||||||
|
serializer = null,
|
||||||
|
initialConfiguration = AddressSyncStep.ASK_BIOMETRY,
|
||||||
|
handleBackButton = true,
|
||||||
|
childFactory = { configuration, factoryContext ->
|
||||||
|
createChild(
|
||||||
|
step = configuration,
|
||||||
|
childContext = childByContext(factoryContext),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
val state by model.uiState.collectAsStateWithLifecycle()
|
AddressSyncContent(
|
||||||
|
modifier = modifier,
|
||||||
|
childContent = {
|
||||||
|
Children(
|
||||||
|
stack = childStack,
|
||||||
|
) { child ->
|
||||||
|
child.instance.Content(Modifier)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
AddressSyncContent(modifier = modifier, state = state)
|
BackHandler {
|
||||||
|
model.onIntent(AddressSyncIntent.Back)
|
||||||
BackHandler(onBack = state.onBackClick)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
private fun createChild(step: AddressSyncStep, childContext: AppComponentContext): ComposableContentComponent {
|
||||||
interface Factory : AddressSyncComponent.Factory {
|
return when (step) {
|
||||||
override fun create(
|
AddressSyncStep.ASK_BIOMETRY -> createAskBiometryComponent(childContext)
|
||||||
context: AppComponentContext,
|
AddressSyncStep.ASK_NOTIFICATIONS -> createPushNotificationComponent(childContext)
|
||||||
params: AddressSyncComponent.Params,
|
AddressSyncStep.ADDRESS_SYNC -> TODO("Will be implemented during [REDACTED_TASK_KEY]")
|
||||||
): DefaultAddressSyncComponent
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAskBiometryComponent(childContext: AppComponentContext): AskBiometryComponent {
|
||||||
|
return askBiometryComponentFactory.create(
|
||||||
|
context = childContext,
|
||||||
|
params = AskBiometryComponent.Params(
|
||||||
|
isBottomSheetVariant = false,
|
||||||
|
modelCallbacks = object : AskBiometryComponent.ModelCallbacks {
|
||||||
|
override fun onAllowed() {
|
||||||
|
model.onIntent(
|
||||||
|
AddressSyncIntent.Next(
|
||||||
|
step = AddressSyncStep.ASK_NOTIFICATIONS,
|
||||||
|
shouldReplace = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDenied() {
|
||||||
|
model.onIntent(
|
||||||
|
AddressSyncIntent.Next(
|
||||||
|
step = AddressSyncStep.ASK_NOTIFICATIONS,
|
||||||
|
shouldReplace = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPushNotificationComponent(childContext: AppComponentContext): PushNotificationsComponent {
|
||||||
|
return pushNotificationsComponentFactory.create(
|
||||||
|
context = childContext,
|
||||||
|
params = PushNotificationsParams(
|
||||||
|
modelCallbacks = object : PushNotificationsModelCallbacks {
|
||||||
|
override fun onAllowSystemPermission() {
|
||||||
|
model.onIntent(
|
||||||
|
AddressSyncIntent.Next(
|
||||||
|
step = AddressSyncStep.ADDRESS_SYNC,
|
||||||
|
shouldReplace = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDenySystemPermission() {
|
||||||
|
model.onIntent(
|
||||||
|
AddressSyncIntent.Next(
|
||||||
|
step = AddressSyncStep.ADDRESS_SYNC,
|
||||||
|
shouldReplace = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDismiss() {
|
||||||
|
model.onIntent(
|
||||||
|
AddressSyncIntent.Next(
|
||||||
|
step = AddressSyncStep.ADDRESS_SYNC,
|
||||||
|
shouldReplace = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
source = AppRoute.PushNotification.Source.Onboarding,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package com.tangem.features.onboarding.v2.addresssync.di
|
|
||||||
|
|
||||||
import com.tangem.features.onboarding.v2.addresssync.AddressSyncComponent
|
|
||||||
import com.tangem.features.onboarding.v2.addresssync.DefaultAddressSyncComponent
|
|
||||||
import dagger.Binds
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
internal interface AddressSyncComponentModule {
|
|
||||||
|
|
||||||
@Binds
|
|
||||||
@Singleton
|
|
||||||
fun bindAddressSyncComponentFactory(factory: DefaultAddressSyncComponent.Factory): AddressSyncComponent.Factory
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package com.tangem.features.onboarding.v2.addresssync.entity
|
|
||||||
|
|
||||||
internal data class AddressSyncUM(
|
|
||||||
val onBackClick: () -> Unit,
|
|
||||||
)
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.tangem.features.onboarding.v2.addresssync.model
|
||||||
|
|
||||||
|
import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
|
||||||
|
|
||||||
|
sealed interface AddressSyncIntent {
|
||||||
|
data class Next(val step: AddressSyncStep, val shouldReplace: Boolean) : AddressSyncIntent
|
||||||
|
data object Back : AddressSyncIntent
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,68 @@
|
||||||
package com.tangem.features.onboarding.v2.addresssync.model
|
package com.tangem.features.onboarding.v2.addresssync.model
|
||||||
|
|
||||||
|
import com.arkivanov.decompose.DelicateDecomposeApi
|
||||||
|
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||||
|
import com.arkivanov.decompose.router.stack.pop
|
||||||
|
import com.arkivanov.decompose.router.stack.push
|
||||||
|
import com.arkivanov.decompose.router.stack.replaceCurrent
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.features.onboarding.v2.addresssync.entity.AddressSyncUM
|
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||||
|
import com.tangem.domain.settings.ShouldAskPermissionUseCase
|
||||||
|
import com.tangem.domain.settings.ShouldShowAskBiometryUseCase
|
||||||
|
import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
|
||||||
|
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@OptIn(DelicateDecomposeApi::class)
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
internal class AddressSyncModel @Inject constructor(
|
internal class AddressSyncModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val shouldShowAskBiometryUseCase: ShouldShowAskBiometryUseCase,
|
||||||
|
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||||
|
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
val uiState: StateFlow<AddressSyncUM>
|
val stackNavigation = StackNavigation<AddressSyncStep>()
|
||||||
field: MutableStateFlow<AddressSyncUM> = MutableStateFlow(getInitialState())
|
|
||||||
|
|
||||||
private fun getInitialState(): AddressSyncUM {
|
fun onIntent(intent: AddressSyncIntent) {
|
||||||
TODO("Will be implemented during [REDACTED_TASK_KEY]")
|
when (intent) {
|
||||||
|
is AddressSyncIntent.Next -> nextScreen(intent)
|
||||||
|
AddressSyncIntent.Back -> goBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun nextScreen(next: AddressSyncIntent.Next) {
|
||||||
|
val (nextStep, replace) = next
|
||||||
|
if (replace) {
|
||||||
|
stackNavigation.replaceCurrent(configuration = nextStep)
|
||||||
|
} else {
|
||||||
|
stackNavigation.push(configuration = nextStep)
|
||||||
|
}
|
||||||
|
modelScope.launch { trySkippingScreen(next) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun trySkippingScreen(next: AddressSyncIntent.Next) {
|
||||||
|
when (next.step) {
|
||||||
|
AddressSyncStep.ASK_BIOMETRY -> {
|
||||||
|
val shouldShowAskBiometry = canUseBiometryUseCase.strict() && shouldShowAskBiometryUseCase()
|
||||||
|
if (shouldShowAskBiometry.not()) {
|
||||||
|
nextScreen(AddressSyncIntent.Next(step = AddressSyncStep.ASK_NOTIFICATIONS, shouldReplace = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddressSyncStep.ASK_NOTIFICATIONS -> {
|
||||||
|
val shouldShowAskNotification = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||||
|
if (shouldShowAskNotification.not()) {
|
||||||
|
nextScreen(AddressSyncIntent.Next(step = AddressSyncStep.ADDRESS_SYNC, shouldReplace = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddressSyncStep.ADDRESS_SYNC -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun goBack() {
|
||||||
|
stackNavigation.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.tangem.features.onboarding.v2.addresssync.navigation
|
||||||
|
|
||||||
|
enum class AddressSyncStep {
|
||||||
|
ASK_BIOMETRY,
|
||||||
|
ASK_NOTIFICATIONS,
|
||||||
|
ADDRESS_SYNC,
|
||||||
|
}
|
||||||
|
|
@ -2,20 +2,19 @@ package com.tangem.features.onboarding.v2.addresssync.ui
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
|
||||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.features.onboarding.v2.addresssync.entity.AddressSyncUM
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AddressSyncContent(state: AddressSyncUM, modifier: Modifier = Modifier) {
|
internal fun AddressSyncContent(modifier: Modifier = Modifier, childContent: @Composable (Modifier) -> Unit = {}) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(color = TangemTheme.colors.background.secondary)
|
.background(color = TangemTheme.colors.background.secondary)
|
||||||
|
|
@ -24,31 +23,15 @@ internal fun AddressSyncContent(state: AddressSyncUM, modifier: Modifier = Modif
|
||||||
.systemBarsPadding(),
|
.systemBarsPadding(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
AppBarWithBackButton(
|
childContent(Modifier)
|
||||||
onBackClick = state.onBackClick,
|
|
||||||
modifier = Modifier.height(TangemTheme.dimens.size56),
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
|
||||||
.weight(1f),
|
|
||||||
) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true, widthDp = 360)
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
private fun AddressSyncContentPreview(@PreviewParameter(PreviewStateProvider::class) state: AddressSyncUM) {
|
private fun AddressSyncContentPreview() {
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
AddressSyncContent(state = state)
|
AddressSyncContent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PreviewStateProvider : CollectionPreviewParameterProvider<AddressSyncUM>(
|
|
||||||
buildList {
|
|
||||||
TODO("Will be implemented during [REDACTED_TASK_KEY]")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
@ -82,6 +82,7 @@ internal class OnboardingEntryModel @Inject constructor(
|
||||||
is Mode.UpgradeHotWallet -> OnboardingMultiWalletComponent.Mode.UpgradeHotWallet(
|
is Mode.UpgradeHotWallet -> OnboardingMultiWalletComponent.Mode.UpgradeHotWallet(
|
||||||
userWalletId = mode.userWalletId,
|
userWalletId = mode.userWalletId,
|
||||||
)
|
)
|
||||||
|
is Mode.AddressSync -> OnboardingMultiWalletComponent.Mode.AddressSync
|
||||||
else -> error("Incorrect onboarding type")
|
else -> error("Incorrect onboarding type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ interface OnboardingMultiWalletComponent : ComposableContentComponent, InnerNavi
|
||||||
data object AddBackup : Mode()
|
data object AddBackup : Mode()
|
||||||
data object ContinueFinalize : Mode()
|
data object ContinueFinalize : Mode()
|
||||||
data class UpgradeHotWallet(val userWalletId: UserWalletId) : Mode()
|
data class UpgradeHotWallet(val userWalletId: UserWalletId) : Mode()
|
||||||
|
data object AddressSync : Mode()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Factory : ComponentFactory<Params, OnboardingMultiWalletComponent>
|
interface Factory : ComponentFactory<Params, OnboardingMultiWalletComponent>
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,10 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
import com.tangem.core.decompose.navigation.inner.InnerNavigation
|
import com.tangem.core.decompose.navigation.inner.InnerNavigation
|
||||||
import com.tangem.core.decompose.navigation.inner.InnerNavigationState
|
import com.tangem.core.decompose.navigation.inner.InnerNavigationState
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
import com.tangem.features.biometry.AskBiometryComponent
|
||||||
|
import com.tangem.features.onboarding.v2.addresssync.DefaultAddressSyncComponent
|
||||||
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
|
import com.tangem.features.onboarding.v2.common.analytics.OnboardingEvent
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.MultiWalletAccessCodeComponent
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.accesscode.MultiWalletAccessCodeComponent
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent
|
||||||
|
|
@ -42,6 +44,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiW
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.OnboardingMultiWallet
|
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.OnboardingMultiWallet
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworksState
|
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworksState
|
||||||
import com.tangem.features.onboarding.v2.util.ResetCardsComponent
|
import com.tangem.features.onboarding.v2.util.ResetCardsComponent
|
||||||
|
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
|
@ -53,6 +56,8 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
@Assisted private val params: OnboardingMultiWalletComponent.Params,
|
@Assisted private val params: OnboardingMultiWalletComponent.Params,
|
||||||
private val analyticsHandler: AnalyticsEventHandler,
|
private val analyticsHandler: AnalyticsEventHandler,
|
||||||
private val resetCardsComponentFactory: ResetCardsComponent.Factory,
|
private val resetCardsComponentFactory: ResetCardsComponent.Factory,
|
||||||
|
private val askBiometryComponentFactory: AskBiometryComponent.Factory,
|
||||||
|
private val pushNotificationsComponentFactory: PushNotificationsComponent.Factory,
|
||||||
) : OnboardingMultiWalletComponent, AppComponentContext by context {
|
) : OnboardingMultiWalletComponent, AppComponentContext by context {
|
||||||
|
|
||||||
private val model: OnboardingMultiWalletModel = getOrCreateModel(params)
|
private val model: OnboardingMultiWalletModel = getOrCreateModel(params)
|
||||||
|
|
@ -60,6 +65,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
private val artworksState = instanceKeeper.getOrCreateSimple(key = "artworksState") {
|
private val artworksState = instanceKeeper.getOrCreateSimple(key = "artworksState") {
|
||||||
MutableStateFlow(
|
MutableStateFlow(
|
||||||
when (model.state.value.currentStep) {
|
when (model.state.value.currentStep) {
|
||||||
|
AddressSync -> WalletArtworksState.Hidden
|
||||||
UpgradeWallet -> WalletArtworksState.Folded
|
UpgradeWallet -> WalletArtworksState.Folded
|
||||||
CreateWallet -> WalletArtworksState.Folded
|
CreateWallet -> WalletArtworksState.Folded
|
||||||
ChooseBackupOption -> WalletArtworksState.Fan
|
ChooseBackupOption -> WalletArtworksState.Fan
|
||||||
|
|
@ -186,6 +192,11 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
onBack = { model.onBack() },
|
onBack = { model.onBack() },
|
||||||
onEvent = ::handleFinalizeComponentEvent,
|
onEvent = ::handleFinalizeComponentEvent,
|
||||||
)
|
)
|
||||||
|
AddressSync -> DefaultAddressSyncComponent(
|
||||||
|
appComponentContext = childContext,
|
||||||
|
askBiometryComponentFactory = askBiometryComponentFactory,
|
||||||
|
pushNotificationsComponentFactory = pushNotificationsComponentFactory,
|
||||||
|
)
|
||||||
Done -> error("Unexpected Done state")
|
Done -> error("Unexpected Done state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
|
||||||
val userWallet = when (params.parentParams.mode) {
|
val userWallet = when (params.parentParams.mode) {
|
||||||
OnboardingMultiWalletComponent.Mode.Onboarding,
|
OnboardingMultiWalletComponent.Mode.Onboarding,
|
||||||
OnboardingMultiWalletComponent.Mode.ContinueFinalize,
|
OnboardingMultiWalletComponent.Mode.ContinueFinalize,
|
||||||
|
OnboardingMultiWalletComponent.Mode.AddressSync,
|
||||||
-> {
|
-> {
|
||||||
saveWalletUseCase.invoke(
|
saveWalletUseCase.invoke(
|
||||||
userWallet = userWalletCreated.copy(
|
userWallet = userWalletCreated.copy(
|
||||||
|
|
|
||||||
|
|
@ -120,8 +120,12 @@ internal class OnboardingMultiWalletModel @Inject constructor(
|
||||||
params.mode is OnboardingMultiWalletComponent.Mode.UpgradeHotWallet -> {
|
params.mode is OnboardingMultiWalletComponent.Mode.UpgradeHotWallet -> {
|
||||||
OnboardingMultiWalletState.Step.UpgradeWallet
|
OnboardingMultiWalletState.Step.UpgradeWallet
|
||||||
}
|
}
|
||||||
params.mode == OnboardingMultiWalletComponent.Mode.ContinueFinalize ->
|
params.mode == OnboardingMultiWalletComponent.Mode.ContinueFinalize -> {
|
||||||
OnboardingMultiWalletState.Step.Finalize
|
OnboardingMultiWalletState.Step.Finalize
|
||||||
|
}
|
||||||
|
params.mode == OnboardingMultiWalletComponent.Mode.AddressSync -> {
|
||||||
|
OnboardingMultiWalletState.Step.AddressSync
|
||||||
|
}
|
||||||
// Add backup button
|
// Add backup button
|
||||||
// Wallet1 without backup and userwallet's scanResponse doesn't contain primary card.
|
// Wallet1 without backup and userwallet's scanResponse doesn't contain primary card.
|
||||||
card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup &&
|
card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup &&
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ data class OnboardingMultiWalletState(
|
||||||
SeedPhrase,
|
SeedPhrase,
|
||||||
ScanPrimary,
|
ScanPrimary,
|
||||||
AddBackupDevice,
|
AddBackupDevice,
|
||||||
|
AddressSync,
|
||||||
Finalize,
|
Finalize,
|
||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,5 @@ fun screenTitleByStep(step: OnboardingMultiWalletState.Step): TextReference = wh
|
||||||
OnboardingMultiWalletState.Step.Finalize -> resourceReference(R.string.onboarding_button_finalize_backup)
|
OnboardingMultiWalletState.Step.Finalize -> resourceReference(R.string.onboarding_button_finalize_backup)
|
||||||
OnboardingMultiWalletState.Step.Done -> resourceReference(R.string.common_done)
|
OnboardingMultiWalletState.Step.Done -> resourceReference(R.string.common_done)
|
||||||
OnboardingMultiWalletState.Step.UpgradeWallet -> resourceReference(R.string.common_tangem)
|
OnboardingMultiWalletState.Step.UpgradeWallet -> resourceReference(R.string.common_tangem)
|
||||||
|
OnboardingMultiWalletState.Step.AddressSync -> TODO("Will be implemented during [REDACTED_TASK_KEY]")
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
package com.tangem.features.onboarding.v2.addresssync.model
|
||||||
|
|
||||||
|
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||||
|
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||||
|
import com.tangem.domain.settings.ShouldAskPermissionUseCase
|
||||||
|
import com.tangem.domain.settings.ShouldShowAskBiometryUseCase
|
||||||
|
import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
|
||||||
|
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||||
|
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
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 AddressSyncModelTest {
|
||||||
|
|
||||||
|
private val shouldShowAskBiometryUseCase: ShouldShowAskBiometryUseCase = mockk()
|
||||||
|
private val canUseBiometryUseCase: CanUseBiometryUseCase = mockk()
|
||||||
|
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase = mockk()
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
coEvery { canUseBiometryUseCase.strict() } returns false
|
||||||
|
coEvery { shouldShowAskBiometryUseCase() } returns false
|
||||||
|
coEvery { shouldAskPermissionUseCase(PUSH_PERMISSION) } returns false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN biometry allowed AND should show biometry WHEN Next ASK_BIOMETRY THEN ASK_BIOMETRY on top`() = runTest {
|
||||||
|
coEvery { canUseBiometryUseCase.strict() } returns true
|
||||||
|
coEvery { shouldShowAskBiometryUseCase() } returns true
|
||||||
|
|
||||||
|
val model = createModel(this)
|
||||||
|
val stack = model.stackNavigation.trackStack()
|
||||||
|
|
||||||
|
model.onIntent(AddressSyncIntent.Next(step = AddressSyncStep.ASK_BIOMETRY, shouldReplace = false))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assert(stack == listOf(AddressSyncStep.ASK_BIOMETRY))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN biometry skipped AND notifications required WHEN Next ASK_BIOMETRY THEN ASK_NOTIFICATIONS on top`() =
|
||||||
|
runTest {
|
||||||
|
coEvery { canUseBiometryUseCase.strict() } returns true
|
||||||
|
coEvery { shouldShowAskBiometryUseCase() } returns false
|
||||||
|
coEvery { shouldAskPermissionUseCase(PUSH_PERMISSION) } returns true
|
||||||
|
|
||||||
|
val model = createModel(this)
|
||||||
|
val stack = model.stackNavigation.trackStack()
|
||||||
|
|
||||||
|
model.onIntent(AddressSyncIntent.Next(step = AddressSyncStep.ASK_BIOMETRY, shouldReplace = false))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assert(stack == listOf(AddressSyncStep.ASK_NOTIFICATIONS))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN biometry skipped AND notifications skipped WHEN Next ASK_BIOMETRY THEN ADDRESS_SYNC on top`() = runTest {
|
||||||
|
coEvery { canUseBiometryUseCase.strict() } returns true
|
||||||
|
coEvery { shouldShowAskBiometryUseCase() } returns false
|
||||||
|
coEvery { shouldAskPermissionUseCase(PUSH_PERMISSION) } returns false
|
||||||
|
|
||||||
|
val model = createModel(this)
|
||||||
|
val stack = model.stackNavigation.trackStack()
|
||||||
|
|
||||||
|
model.onIntent(AddressSyncIntent.Next(step = AddressSyncStep.ASK_BIOMETRY, shouldReplace = false))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assert(stack == listOf(AddressSyncStep.ADDRESS_SYNC))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN notifications required WHEN Next ASK_NOTIFICATIONS THEN ASK_NOTIFICATIONS on top`() = runTest {
|
||||||
|
coEvery { shouldAskPermissionUseCase(PUSH_PERMISSION) } returns true
|
||||||
|
|
||||||
|
val model = createModel(this)
|
||||||
|
val stack = model.stackNavigation.trackStack()
|
||||||
|
|
||||||
|
model.onIntent(AddressSyncIntent.Next(step = AddressSyncStep.ASK_NOTIFICATIONS, shouldReplace = false))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assert(stack == listOf(AddressSyncStep.ASK_NOTIFICATIONS))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN notifications skipped WHEN Next ASK_NOTIFICATIONS THEN ADDRESS_SYNC on top`() = runTest {
|
||||||
|
coEvery { shouldAskPermissionUseCase(PUSH_PERMISSION) } returns false
|
||||||
|
|
||||||
|
val model = createModel(this)
|
||||||
|
val stack = model.stackNavigation.trackStack()
|
||||||
|
|
||||||
|
model.onIntent(AddressSyncIntent.Next(step = AddressSyncStep.ASK_NOTIFICATIONS, shouldReplace = false))
|
||||||
|
advanceUntilIdle()
|
||||||
|
|
||||||
|
assert(stack == listOf(AddressSyncStep.ADDRESS_SYNC))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StackNavigation<AddressSyncStep>.trackStack(): List<AddressSyncStep> {
|
||||||
|
val tracked = mutableListOf<AddressSyncStep>()
|
||||||
|
subscribe { event ->
|
||||||
|
val newStack = event.transformer(tracked.toList())
|
||||||
|
tracked.clear()
|
||||||
|
tracked.addAll(newStack)
|
||||||
|
}
|
||||||
|
return tracked
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createModel(testScope: TestScope): AddressSyncModel {
|
||||||
|
return AddressSyncModel(
|
||||||
|
dispatchers = testScope.createTestingCoroutineDispatcherProvider(),
|
||||||
|
shouldShowAskBiometryUseCase = shouldShowAskBiometryUseCase,
|
||||||
|
canUseBiometryUseCase = canUseBiometryUseCase,
|
||||||
|
shouldAskPermissionUseCase = shouldAskPermissionUseCase,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TestScope.createTestingCoroutineDispatcherProvider(): TestingCoroutineDispatcherProvider {
|
||||||
|
val testDispatcher = StandardTestDispatcher(testScheduler)
|
||||||
|
return TestingCoroutineDispatcherProvider(
|
||||||
|
main = testDispatcher,
|
||||||
|
mainImmediate = testDispatcher,
|
||||||
|
io = testDispatcher,
|
||||||
|
default = testDispatcher,
|
||||||
|
single = testDispatcher,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue