Updated on 2026-08-14
This commit is contained in:
parent
df8f9abe3b
commit
e3ff3e1294
24 changed files with 238 additions and 83 deletions
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.features.onboarding.v2
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface TitleProvider {
|
||||
val currentTitle: StateFlow<TextReference>
|
||||
fun changeTitle(text: TextReference)
|
||||
val currentTitle: StateFlow<OnboardingTitle>
|
||||
fun changeTitle(title: OnboardingTitle)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.onboarding.v2.addresssync
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -23,15 +24,18 @@ import com.tangem.features.onboarding.v2.addresssync.model.AddressSyncState
|
|||
import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
|
||||
import com.tangem.features.onboarding.v2.addresssync.ui.AddressSyncButtonScreen
|
||||
import com.tangem.features.onboarding.v2.addresssync.ui.AddressSyncContent
|
||||
import com.tangem.features.onboarding.v2.addresssync.ui.loading.AddressSyncLoading
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||
import com.tangem.features.pushnotifications.api.PushNotificationsParams
|
||||
|
||||
@OptIn(DelicateDecomposeApi::class)
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultAddressSyncComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: MultiWalletChildParams,
|
||||
private val onBack: () -> Unit,
|
||||
private val addressSyncParams: AddressSyncComponent.Params,
|
||||
private val askBiometryComponentFactory: AskBiometryComponent.Factory,
|
||||
private val pushNotificationsComponentFactory: PushNotificationsComponent.Factory,
|
||||
|
|
@ -44,7 +48,7 @@ internal class DefaultAddressSyncComponent(
|
|||
key = "innerStack",
|
||||
source = model.stackNavigation,
|
||||
serializer = null,
|
||||
initialConfiguration = AddressSyncStep.ASK_BIOMETRY,
|
||||
initialConfiguration = AddressSyncStep.LOADING,
|
||||
handleBackButton = true,
|
||||
childFactory = { configuration, factoryContext ->
|
||||
createChild(
|
||||
|
|
@ -54,8 +58,29 @@ internal class DefaultAddressSyncComponent(
|
|||
},
|
||||
)
|
||||
|
||||
init {
|
||||
model.initConfiguration()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
BackHandler(enabled = true) {
|
||||
onBack()
|
||||
}
|
||||
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(state) {
|
||||
when (state) {
|
||||
AddressSyncState.Exit -> handleExit()
|
||||
is AddressSyncState.Success -> {
|
||||
val shouldExit = (state as AddressSyncState.Success).shouldExit
|
||||
if (shouldExit) handleExit()
|
||||
}
|
||||
AddressSyncState.Loading -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
AddressSyncContent(
|
||||
modifier = modifier,
|
||||
childContent = {
|
||||
|
|
@ -68,14 +93,23 @@ internal class DefaultAddressSyncComponent(
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleExit() {
|
||||
if (addressSyncParams.isWalletStarted) {
|
||||
router.popTo(AppRoute.Wallet)
|
||||
} else {
|
||||
router.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createChild(step: AddressSyncStep, childContext: AppComponentContext): ComposableContentComponent {
|
||||
return when (step) {
|
||||
AddressSyncStep.LOADING -> ComposableContentComponent { AddressSyncLoading() }
|
||||
AddressSyncStep.ASK_BIOMETRY -> createAskBiometryComponent(childContext)
|
||||
AddressSyncStep.ASK_NOTIFICATIONS -> createPushNotificationComponent(childContext)
|
||||
AddressSyncStep.ADDRESS_SYNC -> ComposableContentComponent {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
when (state) {
|
||||
AddressSyncState.Loading -> Unit // todo shimmers will be implemented during [REDACTED_TASK_KEY]
|
||||
AddressSyncState.Loading -> AddressSyncLoading()
|
||||
is AddressSyncState.Success -> AddressSyncButtonScreen(
|
||||
state = state as AddressSyncState.Success,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -83,13 +117,7 @@ internal class DefaultAddressSyncComponent(
|
|||
model.onIntent(AddressSyncIntent.Sync)
|
||||
},
|
||||
)
|
||||
AddressSyncState.Exit -> LaunchedEffect(Unit) {
|
||||
if (addressSyncParams.isWalletStarted) {
|
||||
router.popTo(AppRoute.Wallet)
|
||||
} else {
|
||||
router.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
AddressSyncState.Exit -> AddressSyncLoading()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ internal sealed class AddressSyncState {
|
|||
data class Success(
|
||||
val currencies: List<CryptoCurrency>,
|
||||
val isButtonLoading: Boolean = false,
|
||||
val shouldExit: Boolean = false,
|
||||
) : AddressSyncState() {
|
||||
val currenciesCount: Int = currencies.size
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
|
|||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.MultiWalletInnerNavigationState
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
|
|
@ -59,9 +60,6 @@ internal class AddressSyncModel @Inject constructor(
|
|||
stackSize = AddressSyncStep.ASK_BIOMETRY.pageNumber,
|
||||
stackMaxSize = ADDRESS_SYNC_MAX_STEPS,
|
||||
)
|
||||
modelScope.launch {
|
||||
trySkippingScreen(AddressSyncStep.ASK_BIOMETRY)
|
||||
}
|
||||
fetchWalletCrypto()
|
||||
}
|
||||
|
||||
|
|
@ -76,24 +74,31 @@ internal class AddressSyncModel @Inject constructor(
|
|||
stackNavigation.replaceCurrent(configuration = next.step)
|
||||
updateStepperPage(next)
|
||||
updateTitle(next)
|
||||
modelScope.launch { trySkippingScreen(next.step) }
|
||||
modelScope.launch { tryToSkipNotificationScreen(next.step) }
|
||||
}
|
||||
|
||||
private suspend fun trySkippingScreen(step: AddressSyncStep) {
|
||||
when (step) {
|
||||
AddressSyncStep.ASK_BIOMETRY -> {
|
||||
val shouldShowAskBiometry = canUseBiometryUseCase.strict() && shouldShowAskBiometryUseCase()
|
||||
if (shouldShowAskBiometry.not()) {
|
||||
nextScreen(AddressSyncIntent.Next(step = AddressSyncStep.ASK_NOTIFICATIONS))
|
||||
}
|
||||
fun initConfiguration() {
|
||||
modelScope.launch {
|
||||
val shouldShowAskBiometry = canUseBiometryUseCase.strict() && shouldShowAskBiometryUseCase()
|
||||
val shouldShowAskNotification = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||
val step = when {
|
||||
!shouldShowAskBiometry && !shouldShowAskNotification -> AddressSyncStep.ADDRESS_SYNC
|
||||
!shouldShowAskBiometry -> AddressSyncStep.ASK_NOTIFICATIONS
|
||||
else -> AddressSyncStep.ASK_BIOMETRY
|
||||
}
|
||||
nextScreen(AddressSyncIntent.Next(step))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun tryToSkipNotificationScreen(step: AddressSyncStep) {
|
||||
when (step) {
|
||||
AddressSyncStep.ASK_NOTIFICATIONS -> {
|
||||
val shouldShowAskNotification = shouldAskPermissionUseCase(PUSH_PERMISSION)
|
||||
if (shouldShowAskNotification.not()) {
|
||||
nextScreen(AddressSyncIntent.Next(step = AddressSyncStep.ADDRESS_SYNC))
|
||||
}
|
||||
}
|
||||
AddressSyncStep.ADDRESS_SYNC -> Unit
|
||||
AddressSyncStep.LOADING, AddressSyncStep.ASK_BIOMETRY, AddressSyncStep.ADDRESS_SYNC -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -106,9 +111,14 @@ internal class AddressSyncModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun updateTitle(next: AddressSyncIntent.Next) {
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
text = resourceReference(next.step.stringId),
|
||||
)
|
||||
next.step.stringId?.let { stringId ->
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(stringId),
|
||||
shouldForceTitle = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchWalletCrypto() {
|
||||
|
|
@ -164,7 +174,10 @@ internal class AddressSyncModel @Inject constructor(
|
|||
launch { fetchNetworks(cryptoCurrencies) },
|
||||
launch { fetchStaking(cryptoCurrencies) },
|
||||
).joinAll()
|
||||
state.value = AddressSyncState.Exit
|
||||
state.update { addressSyncState ->
|
||||
addressSyncState as AddressSyncState.Success
|
||||
addressSyncState.copy(shouldExit = true)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ package com.tangem.features.onboarding.v2.addresssync.navigation
|
|||
import androidx.annotation.StringRes
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
|
||||
internal enum class AddressSyncStep(val pageNumber: Int, @StringRes val stringId: Int) {
|
||||
internal enum class AddressSyncStep(val pageNumber: Int, @StringRes val stringId: Int?) {
|
||||
LOADING(pageNumber = 0, stringId = null),
|
||||
ASK_BIOMETRY(pageNumber = 1, stringId = R.string.onboarding_navbar_title_biometrics),
|
||||
ASK_NOTIFICATIONS(pageNumber = 2, stringId = R.string.onboarding_title_notifications),
|
||||
ADDRESS_SYNC(pageNumber = 3, stringId = R.string.onboarding_navbar_title_last_step),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.onboarding.v2.addresssync.ui.loading
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
@Composable
|
||||
fun AddressSyncLoading(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size30)
|
||||
.padding(4.dp),
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun AddressSyncLoadingPreview() {
|
||||
TangemThemePreview {
|
||||
AddressSyncLoading()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,8 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.stack.*
|
||||
import com.arkivanov.decompose.router.stack.ChildStack
|
||||
import com.arkivanov.decompose.router.stack.childStack
|
||||
import com.arkivanov.decompose.value.ObserveLifecycleMode
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.arkivanov.decompose.value.subscribe
|
||||
|
|
@ -22,6 +23,7 @@ import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute
|
|||
import com.tangem.features.onboarding.v2.entry.impl.ui.OnboardingEntry
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -111,8 +113,14 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor(
|
|||
}
|
||||
}.saveIn(innerNavigationLinkJobHolder)
|
||||
} else {
|
||||
stepperComponent.state.update {
|
||||
it.copy(
|
||||
val titleText = when (stack.active.configuration) {
|
||||
is OnboardingRoute.ManageTokens -> resourceReference(R.string.main_manage_tokens)
|
||||
is OnboardingRoute.AskBiometry -> resourceReference(R.string.onboarding_navbar_save_wallet)
|
||||
is OnboardingRoute.Done -> resourceReference(R.string.onboarding_done_header)
|
||||
else -> error("Unsupported route")
|
||||
}
|
||||
stepperComponent.state.update { stepperState ->
|
||||
stepperState.copy(
|
||||
currentStep = when (stack.active.configuration) {
|
||||
is OnboardingRoute.ManageTokens -> 7
|
||||
is OnboardingRoute.AskBiometry -> 8
|
||||
|
|
@ -120,12 +128,7 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor(
|
|||
else -> error("Unsupported route")
|
||||
},
|
||||
steps = 9,
|
||||
title = when (stack.active.configuration) {
|
||||
is OnboardingRoute.ManageTokens -> resourceReference(R.string.main_manage_tokens)
|
||||
is OnboardingRoute.AskBiometry -> resourceReference(R.string.onboarding_navbar_save_wallet)
|
||||
is OnboardingRoute.Done -> resourceReference(R.string.onboarding_done_header)
|
||||
else -> error("Unsupported route")
|
||||
},
|
||||
title = OnboardingTitle(titleText),
|
||||
showProgress = true,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ 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.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
|
|
@ -26,6 +25,7 @@ import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent.Mode
|
|||
import com.tangem.features.onboarding.v2.entry.impl.analytics.OnboardingEntryEvent
|
||||
import com.tangem.features.onboarding.v2.entry.impl.routing.OnboardingRoute
|
||||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import com.tangem.features.onboarding.v2.twin.api.OnboardingTwinComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
|
|
@ -51,9 +51,14 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
|
||||
val stackNavigation = StackNavigation<OnboardingRoute>()
|
||||
val titleProvider = object : TitleProvider {
|
||||
override val currentTitle = MutableStateFlow(stringReference(""))
|
||||
override fun changeTitle(text: TextReference) {
|
||||
currentTitle.value = text
|
||||
override val currentTitle = MutableStateFlow(
|
||||
OnboardingTitle(
|
||||
text = stringReference(""),
|
||||
),
|
||||
)
|
||||
|
||||
override fun changeTitle(title: OnboardingTitle) {
|
||||
currentTitle.value = title
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
|||
addressSyncParams = AddressSyncComponent.Params(
|
||||
isWalletStarted = mode.isWalletStarted,
|
||||
),
|
||||
onBack = { model.onBack() },
|
||||
askBiometryComponentFactory = askBiometryComponentFactory,
|
||||
pushNotificationsComponentFactory = pushNotificationsComponentFactory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.ui.MultiWalletBackup
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -33,7 +34,9 @@ class MultiWalletBackupComponent(
|
|||
}
|
||||
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
text = resourceReference(R.string.onboarding_navbar_title_creating_backup),
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(R.string.onboarding_navbar_title_creating_backup),
|
||||
),
|
||||
)
|
||||
|
||||
componentScope.launch {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.model.Wallet1ChooseOptionModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.ui.Wallet1ChooseOption
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -32,7 +33,9 @@ class Wallet1ChooseOptionComponent(
|
|||
}
|
||||
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
text = resourceReference(R.string.onboarding_getting_started),
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(R.string.onboarding_getting_started),
|
||||
),
|
||||
)
|
||||
|
||||
componentScope.launch {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.MultiWalletCreateWallet
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -35,7 +36,9 @@ internal class MultiWalletCreateWalletComponent(
|
|||
}
|
||||
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
text = resourceReference(R.string.onboarding_create_wallet_header),
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(R.string.onboarding_create_wallet_header),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.model.MultiWalletFinalizeModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.finalize.ui.MultiWalletFinalize
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import com.tangem.features.onboarding.v2.util.ResetCardsComponent
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
|
@ -49,7 +50,9 @@ internal class MultiWalletFinalizeComponent(
|
|||
)
|
||||
}
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
resourceReference(R.string.onboarding_button_finalize_backup),
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(R.string.onboarding_button_finalize_backup),
|
||||
),
|
||||
)
|
||||
|
||||
componentScope.launch {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.features.onboarding.v2.impl.R
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.model.MultiWalletScanPrimaryModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.scanprimary.ui.MultiWalletScanPrimary
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -30,7 +31,9 @@ internal class MultiWalletScanPrimaryComponent(
|
|||
}
|
||||
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
text = resourceReference(R.string.onboarding_navbar_title_creating_backup),
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(R.string.onboarding_navbar_title_creating_backup),
|
||||
),
|
||||
)
|
||||
|
||||
componentScope.launch { model.onDone.collect { onDone() } }
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.MultiWalletSeedPhrase
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -33,17 +34,17 @@ internal class MultiWalletSeedPhraseComponent(
|
|||
|
||||
init {
|
||||
componentScope.launch {
|
||||
model.uiState.collect {
|
||||
model.uiState.collect { state ->
|
||||
// change stepper state based on the stack of the current step
|
||||
@Suppress("MagicNumber")
|
||||
params.innerNavigation.update { st ->
|
||||
st.copy(
|
||||
stackSize = 3 + it.order,
|
||||
stackSize = 3 + state.order,
|
||||
stackMaxSize = 11,
|
||||
)
|
||||
}
|
||||
|
||||
val title = when (it) {
|
||||
val title = when (state) {
|
||||
is MultiWalletSeedPhraseUM.Import -> R.string.onboarding_seed_intro_button_import
|
||||
is MultiWalletSeedPhraseUM.GenerateSeedPhrase,
|
||||
is MultiWalletSeedPhraseUM.GeneratedWordsCheck,
|
||||
|
|
@ -51,7 +52,11 @@ internal class MultiWalletSeedPhraseComponent(
|
|||
-> R.string.onboarding_create_wallet_header
|
||||
}
|
||||
|
||||
params.parentParams.titleProvider.changeTitle(text = resourceReference(title))
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(title),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.upgradewallet.model.MultiWalletUpgradeWalletModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.upgradewallet.ui.MultiWalletUpgradeWallet
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -35,7 +36,9 @@ internal class MultiWalletUpgradeWalletComponent(
|
|||
}
|
||||
|
||||
params.parentParams.titleProvider.changeTitle(
|
||||
text = resourceReference(R.string.common_tangem),
|
||||
OnboardingTitle(
|
||||
text = resourceReference(R.string.common_tangem),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletCo
|
|||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.FinalizeStage
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMultiWalletUM
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import com.tangem.operations.attestation.ArtworkSize
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
|
|
@ -72,7 +73,11 @@ internal class OnboardingMultiWalletModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
onboardingRepository.clearUnfinishedFinalizeOnboarding()
|
||||
if (params.mode is OnboardingMultiWalletComponent.Mode.AddressSync) {
|
||||
router.replaceAll(AppRoute.Wallet)
|
||||
if (params.mode.isWalletStarted) {
|
||||
router.popTo(AppRoute.Wallet)
|
||||
} else {
|
||||
router.replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
} else {
|
||||
router.pop()
|
||||
}
|
||||
|
|
@ -177,7 +182,9 @@ internal class OnboardingMultiWalletModel @Inject constructor(
|
|||
|
||||
private fun initScreenTitle() {
|
||||
val title = screenTitleByStep(getInitialStep())
|
||||
params.titleProvider.changeTitle(title)
|
||||
params.titleProvider.changeTitle(
|
||||
title = OnboardingTitle(text = title),
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadCardArtwork() {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.tangem.features.onboarding.v2.note.impl.model.OnboardingNoteCommonSta
|
|||
import com.tangem.features.onboarding.v2.note.impl.model.OnboardingNoteModel
|
||||
import com.tangem.features.onboarding.v2.note.impl.route.ONBOARDING_NOTE_STEPS_COUNT
|
||||
import com.tangem.features.onboarding.v2.note.impl.route.OnboardingNoteRoute
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -82,7 +83,11 @@ internal class DefaultOnboardingNoteComponent @AssistedInject constructor(
|
|||
// sets title and stepper value
|
||||
childStack.subscribe(lifecycle) { stack ->
|
||||
val currentRoute = stack.active.configuration
|
||||
params.titleProvider.changeTitle(TextReference.Res(R.string.onboarding_title))
|
||||
params.titleProvider.changeTitle(
|
||||
title = OnboardingTitle(
|
||||
TextReference.Res(R.string.onboarding_title),
|
||||
),
|
||||
)
|
||||
model.updateStepForNewRoute(currentRoute)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.features.onboarding.v2.stepper.api
|
|||
import androidx.annotation.IntRange
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
internal interface OnboardingStepperComponent : ComposableContentComponent {
|
||||
|
|
@ -12,7 +12,7 @@ internal interface OnboardingStepperComponent : ComposableContentComponent {
|
|||
data class StepperState(
|
||||
@IntRange(from = 0) val currentStep: Int,
|
||||
@IntRange(from = 0) val steps: Int,
|
||||
val title: TextReference,
|
||||
val title: OnboardingTitle,
|
||||
val showProgress: Boolean,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.stepper.api.OnboardingStepperComponent
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
|
||||
@Composable
|
||||
internal fun OnboardingStepper(
|
||||
|
|
@ -51,13 +52,12 @@ internal fun OnboardingStepper(
|
|||
startButton = TopAppBarButtonUM.Back(onBackClick),
|
||||
endButton = TopAppBarButtonUM.Icon(iconRes = R.drawable.ic_chat_24, onClicked = onSupportButtonClick)
|
||||
.takeIf { state.steps != state.currentStep },
|
||||
title = if (state.steps == state.currentStep) {
|
||||
title = if (state.steps == state.currentStep && !state.title.shouldForceTitle) {
|
||||
resourceReference(R.string.common_done)
|
||||
} else {
|
||||
state.title
|
||||
state.title.text
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
TangemLinearProgressIndicator(
|
||||
|
|
@ -89,7 +89,7 @@ private fun OnboardingStepper_Preview() {
|
|||
state = OnboardingStepperComponent.StepperState(
|
||||
currentStep = 2,
|
||||
steps = 3,
|
||||
title = resourceReference(R.string.common_done),
|
||||
title = OnboardingTitle(text = resourceReference(R.string.common_done)),
|
||||
showProgress = true,
|
||||
),
|
||||
onBackClick = {},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.onboarding.v2.title
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class OnboardingTitle(
|
||||
val text: TextReference,
|
||||
val shouldForceTitle: Boolean = false,
|
||||
)
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.decompose.navigation.inner.InnerNavigationHolder
|
|||
import com.tangem.core.decompose.navigation.inner.InnerNavigationState
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.onboarding.v2.impl.R
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import com.tangem.features.onboarding.v2.twin.api.OnboardingTwinComponent
|
||||
import com.tangem.features.onboarding.v2.twin.impl.model.OnboardingTwinModel
|
||||
import com.tangem.features.onboarding.v2.twin.impl.ui.OnboardingTwin
|
||||
|
|
@ -28,7 +29,11 @@ internal class DefaultOnboardingTwinComponent @AssistedInject constructor(
|
|||
private val model: OnboardingTwinModel = getOrCreateModel(params)
|
||||
|
||||
init {
|
||||
params.titleProvider.changeTitle(resourceReference(R.string.twins_recreate_toolbar))
|
||||
params.titleProvider.changeTitle(
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(R.string.twins_recreate_toolbar),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override val innerNavigation: InnerNavigation = object : InnerNavigation {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.decompose.navigation.inner.InnerNavigation
|
||||
import com.tangem.core.decompose.navigation.inner.InnerNavigationState
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVisaAccessCodeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.approve.OnboardingVisaApproveComponent
|
||||
|
|
@ -87,7 +88,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
// sets title and stepper value
|
||||
childStack.subscribe(lifecycle) { stack ->
|
||||
val currentRoute = stack.active.configuration
|
||||
params.titleProvider.changeTitle(currentRoute.screenTitle())
|
||||
params.titleProvider.changeTitle(
|
||||
title = OnboardingTitle(
|
||||
text = currentRoute.screenTitle(),
|
||||
),
|
||||
)
|
||||
model.updateStepForNewRoute(currentRoute)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.features.onboarding.v2.addresssync.navigation.AddressSyncStep
|
|||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.MultiWalletInnerNavigationState
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.title.OnboardingTitle
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
|
|
@ -86,18 +87,19 @@ internal class AddressSyncModelTest {
|
|||
|
||||
val state = testInnerNavigation.value
|
||||
Assertions.assertEquals(AddressSyncStep.ASK_BIOMETRY.pageNumber, state.stackSize)
|
||||
Assertions.assertEquals(AddressSyncStep.entries.size, state.stackMaxSize)
|
||||
Assertions.assertEquals(3, state.stackMaxSize)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN biometry allowed AND should show biometry WHEN Next ASK_BIOMETRY THEN ASK_BIOMETRY on top`() = runTest {
|
||||
fun `GIVEN biometry allowed AND should show biometry WHEN initConfiguration THEN ASK_BIOMETRY on top`() = runTest {
|
||||
coEvery { canUseBiometryUseCase.strict() } returns true
|
||||
coEvery { shouldShowAskBiometryUseCase() } returns true
|
||||
coEvery { shouldAskPermissionUseCase(PUSH_PERMISSION) } returns false
|
||||
|
||||
val model = createModel(this)
|
||||
val stack = model.stackNavigation.trackStack()
|
||||
|
||||
model.onIntent(AddressSyncIntent.Next(step = AddressSyncStep.ASK_BIOMETRY))
|
||||
model.initConfiguration()
|
||||
advanceUntilIdle()
|
||||
|
||||
Assertions.assertEquals(listOf(AddressSyncStep.ASK_BIOMETRY), stack)
|
||||
|
|
@ -105,16 +107,16 @@ internal class AddressSyncModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN biometry skipped AND notifications required WHEN Next ASK_BIOMETRY THEN ASK_NOTIFICATIONS on top`() =
|
||||
fun `GIVEN biometry not needed AND notifications required WHEN initConfiguration THEN ASK_NOTIFICATIONS on top`() =
|
||||
runTest {
|
||||
coEvery { canUseBiometryUseCase.strict() } returns true
|
||||
coEvery { canUseBiometryUseCase.strict() } returns false
|
||||
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))
|
||||
model.initConfiguration()
|
||||
advanceUntilIdle()
|
||||
|
||||
Assertions.assertEquals(listOf(AddressSyncStep.ASK_NOTIFICATIONS), stack)
|
||||
|
|
@ -122,20 +124,21 @@ internal class AddressSyncModelTest {
|
|||
}
|
||||
|
||||
@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
|
||||
fun `GIVEN biometry not needed AND notifications skipped WHEN initConfiguration THEN ADDRESS_SYNC on top`() =
|
||||
runTest {
|
||||
coEvery { canUseBiometryUseCase.strict() } returns false
|
||||
coEvery { shouldShowAskBiometryUseCase() } returns false
|
||||
coEvery { shouldAskPermissionUseCase(PUSH_PERMISSION) } returns false
|
||||
|
||||
val model = createModel(this)
|
||||
val stack = model.stackNavigation.trackStack()
|
||||
val model = createModel(this)
|
||||
val stack = model.stackNavigation.trackStack()
|
||||
|
||||
model.onIntent(AddressSyncIntent.Next(step = AddressSyncStep.ASK_BIOMETRY))
|
||||
advanceUntilIdle()
|
||||
model.initConfiguration()
|
||||
advanceUntilIdle()
|
||||
|
||||
Assertions.assertEquals(listOf(AddressSyncStep.ADDRESS_SYNC), stack)
|
||||
assertStepperAndTitleFor(AddressSyncStep.ADDRESS_SYNC)
|
||||
}
|
||||
Assertions.assertEquals(listOf(AddressSyncStep.ADDRESS_SYNC), stack)
|
||||
assertStepperAndTitleFor(AddressSyncStep.ADDRESS_SYNC)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notifications required WHEN Next ASK_NOTIFICATIONS THEN ASK_NOTIFICATIONS on top`() = runTest {
|
||||
|
|
@ -244,12 +247,13 @@ internal class AddressSyncModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN success state WHEN Sync THEN state becomes Exit`() = runTest {
|
||||
fun `GIVEN success state WHEN Sync THEN state becomes Success with shouldExit`() = runTest {
|
||||
val currencies = listOf<CryptoCurrency>(
|
||||
mockk { every { network } returns mockk() },
|
||||
mockk { every { network } returns mockk() },
|
||||
mockk { every { network } returns mockk() },
|
||||
)
|
||||
val expected = AddressSyncState.Success(currencies, isButtonLoading = true, shouldExit = true)
|
||||
every { multiAccountListSupplier() } returns flowOf(
|
||||
listOf(
|
||||
AccountList.empty(
|
||||
|
|
@ -274,7 +278,7 @@ internal class AddressSyncModelTest {
|
|||
coVerify { derivePublicKeysUseCase(walletId, currencies) }
|
||||
coVerify { multiNetworkStatusFetcher.invoke(any()) }
|
||||
coVerify { multiStakingBalanceFetcher(any()) }
|
||||
Assertions.assertEquals(AddressSyncState.Exit, model.state.value)
|
||||
Assertions.assertEquals(expected, model.state.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -307,7 +311,14 @@ internal class AddressSyncModelTest {
|
|||
|
||||
private fun assertStepperAndTitleFor(step: AddressSyncStep) {
|
||||
Assertions.assertEquals(step.pageNumber, testInnerNavigation.value.stackSize)
|
||||
verify { titleProvider.changeTitle(resourceReference(step.stringId)) }
|
||||
verify {
|
||||
titleProvider.changeTitle(
|
||||
title = OnboardingTitle(
|
||||
text = resourceReference(step.stringId!!),
|
||||
shouldForceTitle = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun StackNavigation<AddressSyncStep>.trackStack(): List<AddressSyncStep> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue