Updated on 2026-08-14
This commit is contained in:
parent
cf05b371ef
commit
e080401183
103 changed files with 142 additions and 10555 deletions
|
|
@ -158,7 +158,6 @@ dependencies {
|
|||
implementation(projects.data.blockaid)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.onboarding)
|
||||
implementation(projects.features.referral.impl)
|
||||
implementation(projects.features.referral.domain)
|
||||
implementation(projects.features.referral.data)
|
||||
|
|
|
|||
|
|
@ -62,13 +62,11 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
|
|||
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.sdk.extensions.init
|
||||
import com.tangem.tap.common.ActivityResultCallbackHolder
|
||||
import com.tangem.tap.common.DialogManager
|
||||
import com.tangem.tap.common.OnActivityResultCallback
|
||||
|
|
@ -83,7 +81,6 @@ import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandle
|
|||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.features.main.MainViewModel
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.tap.routing.component.RoutingComponent
|
||||
|
|
@ -181,9 +178,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var backupServiceHolder: BackupServiceHolder
|
||||
|
||||
@Inject
|
||||
lateinit var onboardingV2FeatureToggles: OnboardingV2FeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var setGoogleServicesAvailabilityUseCase: SetGoogleServicesAvailabilityUseCase
|
||||
|
||||
|
|
@ -364,12 +358,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
cardSdkOwner.register(activity = this)
|
||||
tangemSdkManager = injectedTangemSdkManager
|
||||
|
||||
if (onboardingV2FeatureToggles.isOnboardingV2Enabled) {
|
||||
backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this)
|
||||
backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually
|
||||
} else {
|
||||
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
|
||||
}
|
||||
backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this)
|
||||
backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually
|
||||
|
||||
lockUserWalletsTimer = LockUserWalletsTimer(
|
||||
context = this,
|
||||
|
|
@ -662,7 +652,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
}
|
||||
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
viewModel.checkForUnfinishedBackup()
|
||||
}
|
||||
|
||||
private fun observePolkadotAccountHealthCheck() {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ import com.tangem.tap.common.ui.*
|
|||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.WalletActivationErrorDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.WalletAlreadyWasUsedDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -58,8 +60,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
||||
is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context)
|
||||
is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog)
|
||||
is OnboardingDialog.WalletActivationError -> WalletActivationErrorDialog.create(context, state.dialog)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
|
|
@ -143,9 +143,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
messageRes = R.string.unsupported_wc_version,
|
||||
context = context,
|
||||
)
|
||||
is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context)
|
||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(
|
||||
context = context,
|
||||
scanResponse = state.dialog.scanResponse,
|
||||
|
|
@ -154,10 +151,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
context = context,
|
||||
unfinishedBackupScanResponse = state.dialog.scanResponse,
|
||||
)
|
||||
is BackupDialog.ResetBackupCard -> ResetBackupCardDialog.create(
|
||||
context = context,
|
||||
cardId = state.dialog.cardId,
|
||||
)
|
||||
is AppDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
|
||||
title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
|
||||
message = context.getString(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,6 @@ import com.tangem.tap.common.redux.global.globalReducer
|
|||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
|
|
@ -20,10 +16,6 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
return AppState(
|
||||
globalState = globalReducer(action, state),
|
||||
homeState = HomeReducer.reduce(action, state),
|
||||
onboardingNoteState = OnboardingNoteReducer.reduce(action, state),
|
||||
onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
|
||||
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
|
||||
twinCardsState = TwinCardsReducer.reduce(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
|
|
|
|||
|
|
@ -9,15 +9,7 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddlewa
|
|||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
|
|
@ -31,10 +23,6 @@ import org.rekotlin.StateType
|
|||
data class AppState(
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val homeState: HomeState = HomeState(),
|
||||
val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
|
||||
val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(),
|
||||
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
|
||||
val twinCardsState: TwinCardsState = TwinCardsState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
|
|
@ -49,10 +37,6 @@ data class AppState(
|
|||
notificationsMiddleware,
|
||||
GlobalMiddleware.handler,
|
||||
HomeMiddleware.handler,
|
||||
OnboardingNoteMiddleware.handler,
|
||||
OnboardingWalletMiddleware.handler,
|
||||
OnboardingOtherCardsMiddleware.handler,
|
||||
TwinCardsMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.tangem.domain.redux.StateDialog
|
|||
import com.tangem.tap.common.redux.DebugErrorAction
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class GlobalAction : Action {
|
||||
|
|
@ -21,29 +20,6 @@ sealed class GlobalAction : Action {
|
|||
data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
|
||||
object HideDialog : GlobalAction()
|
||||
|
||||
sealed class Onboarding : GlobalAction() {
|
||||
/**
|
||||
* Initiate an onboarding process.
|
||||
* For resuming unfinished backup of standard Wallet see
|
||||
* BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup
|
||||
*/
|
||||
data class Start(
|
||||
val scanResponse: ScanResponse,
|
||||
val source: BackupStartedSource,
|
||||
val canSkipBackup: Boolean = true,
|
||||
) : Onboarding()
|
||||
|
||||
/**
|
||||
* Initiate resuming of unfinished backup for standard Wallet.
|
||||
* See more BackupAction.CheckForUnfinishedBackup
|
||||
*/
|
||||
data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding()
|
||||
|
||||
object Stop : Onboarding()
|
||||
|
||||
data class ShouldResetCardOnCreate(val shouldReset: Boolean) : Onboarding()
|
||||
}
|
||||
|
||||
object ScanFailsCounter {
|
||||
data class ChooseBehavior(
|
||||
val result: CompletionResult<ScanResponse>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||
import com.tangem.utils.extensions.replaceBy
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -12,21 +11,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
val globalState = state.globalState
|
||||
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
val onboardingManager = OnboardingManager(action.scanResponse)
|
||||
globalState.copy(onboardingState = OnboardingState(true, onboardingManager))
|
||||
}
|
||||
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
|
||||
globalState.copy(onboardingState = OnboardingState(true, null))
|
||||
}
|
||||
is GlobalAction.Onboarding.Stop -> {
|
||||
globalState.copy(onboardingState = OnboardingState(false))
|
||||
}
|
||||
is GlobalAction.Onboarding.ShouldResetCardOnCreate -> {
|
||||
globalState.copy(
|
||||
onboardingState = globalState.onboardingState.copy(shouldResetOnCreate = action.shouldReset),
|
||||
)
|
||||
}
|
||||
is GlobalAction.ScanFailsCounter.Increment -> {
|
||||
globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import com.tangem.domain.redux.LegacyAction
|
|||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.AppSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -26,15 +24,6 @@ internal object LegacyMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is LegacyAction.StartOnboardingProcess -> {
|
||||
store.dispatch(
|
||||
GlobalAction.Onboarding.Start(
|
||||
scanResponse = action.scanResponse,
|
||||
source = BackupStartedSource.CreateBackup,
|
||||
canSkipBackup = action.canSkipBackup,
|
||||
),
|
||||
)
|
||||
}
|
||||
is LegacyAction.PrepareDetailsScreen -> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
import com.tangem.feature.onboarding.data.DefaultMnemonicRepository
|
||||
import com.tangem.feature.onboarding.data.MnemonicRepository
|
||||
import com.tangem.sdk.extensions.getWordlist
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class MnemonicModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMnemonicRepository(@ApplicationContext context: Context): MnemonicRepository {
|
||||
return DefaultMnemonicRepository(Wordlist.getWordlist(context))
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import com.tangem.domain.common.extensions.withMainContext
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
|
@ -42,7 +41,6 @@ class TapWalletManager(
|
|||
|
||||
withMainContext {
|
||||
// Order is important
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.core.ui.message.dialog.Dialogs
|
|||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
|
@ -34,9 +33,6 @@ import com.tangem.tap.common.redux.AppDialog
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -208,8 +204,6 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
crossinline onCancel: suspend () -> Unit,
|
||||
crossinline onSuccess: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
|
||||
checkCardWasUsedInApp(
|
||||
scanResponse = scanResponse,
|
||||
onCancel = {
|
||||
|
|
@ -222,38 +216,26 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
Analytics.addContext(scanResponse)
|
||||
onWalletNotCreated()
|
||||
// must check skip backup using card canSkipBackup
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.Onboarding.Start(
|
||||
navigateTo(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
source = BackupStartedSource.Onboarding,
|
||||
canSkipBackup = scanResponse.card.canSkipBackup,
|
||||
mode = AppRoute.Onboarding.Mode.Onboarding,
|
||||
),
|
||||
)
|
||||
val appScreen = OnboardingHelper.whereToNavigate(scanResponse)
|
||||
navigateTo(appScreen) { onProgressStateChange(it) }
|
||||
) { onProgressStateChange(it) }
|
||||
} else {
|
||||
Analytics.setContext(scanResponse)
|
||||
|
||||
val isTwinRefactoringEnabled =
|
||||
store.inject(DaggerGraphState::onboardingV2FeatureToggles).isTwinRefactoringEnabled
|
||||
|
||||
val wasTwinsOnboardingShown =
|
||||
store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase).invokeSync()
|
||||
|
||||
if (scanResponse.twinsIsTwinned() && !wasTwinsOnboardingShown) {
|
||||
onWalletNotCreated()
|
||||
if (isTwinRefactoringEnabled) {
|
||||
navigateTo(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.WelcomeOnlyTwin,
|
||||
),
|
||||
) { onProgressStateChange(it) }
|
||||
} else {
|
||||
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse)))
|
||||
navigateTo(AppRoute.OnboardingTwins) { onProgressStateChange(it) }
|
||||
}
|
||||
navigateTo(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.WelcomeOnlyTwin,
|
||||
),
|
||||
) { onProgressStateChange(it) }
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
onSuccess(scanResponse)
|
||||
|
|
|
|||
|
|
@ -5,21 +5,15 @@ import arrow.core.right
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.core.chain.ResultChain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -40,46 +34,30 @@ class CheckForOnboardingChain(
|
|||
) : ResultChain<ScanCardException, ScanResponse>() {
|
||||
|
||||
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult))
|
||||
|
||||
return when {
|
||||
OnboardingHelper.isOnboardingCase(previousChainResult) -> {
|
||||
Analytics.addContext(previousChainResult)
|
||||
// must check skip backup using card canSkipBackup
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.Onboarding.Start(
|
||||
ScanChainException.OnboardingNeeded(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = previousChainResult,
|
||||
source = BackupStartedSource.Onboarding,
|
||||
canSkipBackup = previousChainResult.card.canSkipBackup,
|
||||
mode = AppRoute.Onboarding.Mode.Onboarding,
|
||||
),
|
||||
)
|
||||
val route = OnboardingHelper.whereToNavigate(previousChainResult)
|
||||
ScanChainException.OnboardingNeeded(route).left()
|
||||
).left()
|
||||
}
|
||||
else -> {
|
||||
Analytics.setContext(previousChainResult)
|
||||
|
||||
val isTwinRefactoringEnabled =
|
||||
store.inject(DaggerGraphState::onboardingV2FeatureToggles).isTwinRefactoringEnabled
|
||||
|
||||
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
|
||||
.invokeSync()
|
||||
|
||||
// If twins was twinned previously but twins welcome not shown
|
||||
if (previousChainResult.twinsIsTwinned() && !wasTwinsOnboardingShown) {
|
||||
if (isTwinRefactoringEnabled) {
|
||||
ScanChainException.OnboardingNeeded(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = previousChainResult,
|
||||
mode = AppRoute.Onboarding.Mode.WelcomeOnlyTwin,
|
||||
),
|
||||
).left()
|
||||
} else {
|
||||
store.dispatchOnMain(
|
||||
TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(previousChainResult)),
|
||||
)
|
||||
ScanChainException.OnboardingNeeded(AppRoute.OnboardingTwins).left()
|
||||
}
|
||||
ScanChainException.OnboardingNeeded(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = previousChainResult,
|
||||
mode = AppRoute.Onboarding.Mode.WelcomeOnlyTwin,
|
||||
),
|
||||
).left()
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
previousChainResult.right()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -16,13 +15,8 @@ import org.rekotlin.Action
|
|||
object DemoHelper {
|
||||
val config = DemoConfig()
|
||||
|
||||
private val demoMiddlewares = listOf(
|
||||
DemoOnboardingNoteMiddleware(),
|
||||
)
|
||||
|
||||
private val disabledActionFeatures = listOf(
|
||||
WalletConnectAction.StartWalletConnect::class.java,
|
||||
BackupAction.StartBackup::class.java,
|
||||
)
|
||||
|
||||
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
|
||||
|
|
@ -35,10 +29,6 @@ object DemoHelper {
|
|||
val scanResponse = getScanResponse(appState) ?: return false
|
||||
if (!scanResponse.isDemoCard()) return false
|
||||
|
||||
demoMiddlewares.forEach {
|
||||
if (it.tryHandle(config, scanResponse, action)) return true
|
||||
}
|
||||
|
||||
disabledActionFeatures.firstOrNull { it == action::class.java }?.let {
|
||||
val uiMessageSender = store.inject(DaggerGraphState::uiMessageSender)
|
||||
uiMessageSender.send(
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package com.tangem.tap.features.demo
|
||||
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
|
||||
|
||||
override fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean {
|
||||
val noteState = store.state.onboardingNoteState
|
||||
|
||||
return when (action) {
|
||||
is OnboardingNoteAction.Balance.Update -> {
|
||||
val walletManager = if (noteState.walletManager != null) {
|
||||
noteState.walletManager
|
||||
} else {
|
||||
val wmFactory = runBlocking {
|
||||
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
|
||||
}
|
||||
val walletManager = wmFactory?.makePrimaryWalletManager(scanResponse).guard {
|
||||
return false
|
||||
}
|
||||
store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
|
||||
walletManager
|
||||
}
|
||||
|
||||
val balanceAmount = config.getBalance(walletManager.wallet.blockchain)
|
||||
val loadedBalance = noteState.walletBalance.copy(
|
||||
value = balanceAmount.value!!,
|
||||
currency = Currency.Blockchain(walletManager.wallet.blockchain, null),
|
||||
state = ProgressState.Done,
|
||||
error = null,
|
||||
criticalError = null,
|
||||
)
|
||||
|
||||
walletManager.wallet.setAmount(balanceAmount)
|
||||
|
||||
scope.launch {
|
||||
withMainContext {
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetCriticalError(loadedBalance.criticalError))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
|
|
@ -33,8 +32,6 @@ import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState
|
|||
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
|
||||
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
|
||||
import com.tangem.tap.features.details.ui.common.utils.*
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -56,7 +53,6 @@ internal class CardSettingsModel @Inject constructor(
|
|||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<CardSettingsComponent.Params>()
|
||||
|
|
@ -200,23 +196,13 @@ internal class CardSettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
if (scanResponse.cardTypesResolver.isTangemTwins()) {
|
||||
if (onboardingV2FeatureToggles.isTwinRefactoringEnabled) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.RecreateWalletTwin,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// needs to prepare twin state if it's twin cards (depends on onboarding refactoring)
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse)))
|
||||
|
||||
cardSettingsInteractor.clear()
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) }
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.RecreateWalletTwin,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val card = scanResponse.card
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
|
|||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.onramp.FetchHotCryptoUseCase
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
|
|
@ -31,6 +32,9 @@ import com.tangem.domain.staking.FetchStakingTokensUseCase
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -58,6 +62,7 @@ internal class MainViewModel @Inject constructor(
|
|||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val imagePreloader: ImagePreloader,
|
||||
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -94,6 +99,13 @@ internal class MainViewModel @Inject constructor(
|
|||
preloadImages()
|
||||
}
|
||||
|
||||
fun checkForUnfinishedBackup() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
|
||||
}
|
||||
}
|
||||
|
||||
/** Loading the resources needed to run the application */
|
||||
private fun loadApplicationResources() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingDialog : StateDialog {
|
||||
object TwinningProcessNotCompleted : OnboardingDialog()
|
||||
data class InterruptOnboarding(val onOk: VoidCallback) : OnboardingDialog()
|
||||
data class WalletActivationError(val onConfirm: () -> Unit) : OnboardingDialog()
|
||||
}
|
||||
|
|
@ -1,46 +1,23 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object OnboardingHelper {
|
||||
suspend fun isOnboardingCase(response: ScanResponse): Boolean {
|
||||
val onboardingManager =
|
||||
store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response)
|
||||
val cardRepository = store.inject(DaggerGraphState::cardRepository)
|
||||
val cardId = response.card.cardId
|
||||
|
||||
return when {
|
||||
response.cardTypesResolver.isVisaWallet() -> {
|
||||
if (response.visaCardActivationStatus == null) error("Visa card activation status is null")
|
||||
|
|
@ -52,227 +29,20 @@ object OnboardingHelper {
|
|||
if (!response.twinsIsTwinned()) {
|
||||
true
|
||||
} else {
|
||||
onboardingManager.isActivationInProgress(cardId)
|
||||
cardRepository.isActivationInProgress(cardId)
|
||||
}
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> {
|
||||
val emptyWallets = response.card.wallets.isEmpty()
|
||||
val activationInProgress = onboardingManager.isActivationInProgress(cardId)
|
||||
val activationInProgress = cardRepository.isActivationInProgress(cardId)
|
||||
val isNoBackup = response.card.backupStatus == CardDTO.BackupStatus.NoBackup &&
|
||||
!DemoHelper.isDemoCard(response)
|
||||
emptyWallets || activationInProgress || isNoBackup
|
||||
}
|
||||
|
||||
response.card.wallets.isNotEmpty() -> onboardingManager.isActivationInProgress(cardId)
|
||||
response.card.wallets.isNotEmpty() -> cardRepository.isActivationInProgress(cardId)
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
|
||||
scanResponse.productType == ProductType.Ring ||
|
||||
scanResponse.productType == ProductType.Wallet ||
|
||||
scanResponse.productType == ProductType.Visa // AppRoute.OnboardingOther is also supported
|
||||
val featureToggles = store.inject(DaggerGraphState::onboardingV2FeatureToggles)
|
||||
if (featureToggles.isOnboardingV2Enabled &&
|
||||
newOnboardingSupportTypes
|
||||
) {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
|
||||
return AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = when (backupState.startedSource) {
|
||||
BackupStartedSource.Onboarding -> AppRoute.Onboarding.Mode.Onboarding
|
||||
BackupStartedSource.CreateBackup -> AppRoute.Onboarding.Mode.AddBackupWallet1
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return when (val type = scanResponse.productType) {
|
||||
ProductType.Note -> if (featureToggles.isNoteRefactoringEnabled) {
|
||||
AppRoute.Onboarding(scanResponse = scanResponse)
|
||||
} else {
|
||||
AppRoute.OnboardingNote
|
||||
}
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
ProductType.Ring,
|
||||
-> if (scanResponse.card.settings.isBackupAllowed) {
|
||||
AppRoute.OnboardingWallet()
|
||||
} else {
|
||||
AppRoute.OnboardingOther
|
||||
}
|
||||
ProductType.Twins -> if (featureToggles.isTwinRefactoringEnabled) {
|
||||
AppRoute.Onboarding(scanResponse = scanResponse)
|
||||
} else {
|
||||
AppRoute.OnboardingTwins
|
||||
}
|
||||
ProductType.Start2Coin,
|
||||
ProductType.Visa,
|
||||
-> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveWallet(
|
||||
alreadyCreatedWallet: UserWallet?,
|
||||
scanResponse: ScanResponse,
|
||||
accessCode: String? = null,
|
||||
backupCardsIds: List<String>? = null,
|
||||
hasBackupError: Boolean = false,
|
||||
) {
|
||||
Analytics.setContext(scanResponse)
|
||||
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
|
||||
when {
|
||||
// When should save user wallets, then save card without navigate to save wallet screen
|
||||
store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.SaveWalletAfterBackup(
|
||||
hasBackupError = hasBackupError,
|
||||
shouldNavigateToWallet = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
||||
// then open save wallet screen
|
||||
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
|
||||
delay(timeMillis = 1_200)
|
||||
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
}
|
||||
}
|
||||
|
||||
fun trySaveWalletAndNavigateToWalletScreen(
|
||||
scanResponse: ScanResponse,
|
||||
accessCode: String? = null,
|
||||
backupCardsIds: List<String>? = null,
|
||||
hasBackupError: Boolean = false,
|
||||
) {
|
||||
Analytics.setContext(scanResponse)
|
||||
scope.launch {
|
||||
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
|
||||
|
||||
when {
|
||||
// When should save user wallets, then save card without navigate to save wallet screen
|
||||
store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.SaveWalletAfterBackup(
|
||||
hasBackupError = hasBackupError,
|
||||
shouldNavigateToWallet = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
||||
// then open save wallet screen
|
||||
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
|
||||
delay(timeMillis = 1_200)
|
||||
|
||||
store.dispatchOnMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
delay(timeMillis = 1_800)
|
||||
store.dispatchNavigationAction { push(AppRoute.SaveWallet) }
|
||||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onInterrupted() {
|
||||
Analytics.removeContext()
|
||||
}
|
||||
|
||||
fun sendToppedUpEvent(scanResponse: ScanResponse) {
|
||||
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
val currency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
|
||||
|
||||
if (userWalletId != null && currency != null) {
|
||||
Analytics.send(Basic.ToppedUp(userWalletId.stringValue, currency))
|
||||
}
|
||||
}
|
||||
|
||||
fun handleTopUpAction(walletManager: WalletManager, scanResponse: ScanResponse) {
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val excludedBlockchains = store.inject(DaggerGraphState::excludedBlockchains)
|
||||
|
||||
val cryptoCurrency = CryptoCurrencyFactory(excludedBlockchains).createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
) ?: return
|
||||
|
||||
val topUpUrl = walletManager.getTopUpUrl(cryptoCurrency) ?: return
|
||||
|
||||
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
|
||||
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
|
||||
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(
|
||||
scanResponse: ScanResponse,
|
||||
backupCardsIds: List<String>?,
|
||||
hasBackupError: Boolean,
|
||||
alreadyCreatedWallet: UserWallet? = null,
|
||||
) {
|
||||
val userWalletBuilderFactory = store.inject(DaggerGraphState::userWalletBuilderFactory)
|
||||
|
||||
val userWallet = alreadyCreatedWallet ?: userWalletBuilderFactory.create(scanResponse)
|
||||
.hasBackupError(hasBackupError)
|
||||
.backupCardsIds(backupCardsIds?.toSet())
|
||||
.build()
|
||||
.guard {
|
||||
Timber.e("User wallet not created")
|
||||
return
|
||||
}
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
.doOnSuccess {
|
||||
mainScope.launch { store.onUserWalletSelected(userWallet) }
|
||||
store.dispatchWithMain(OnboardingWalletAction.WalletSaved(userWallet.walletId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,141 +1,6 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.model.hasPendingTransactions
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Deprecated("Remove when navigation refactoring will be implemented")
|
||||
class OnboardingManager(var scanResponse: ScanResponse) {
|
||||
|
||||
private val cardRepository: CardRepository by lazy { store.inject(DaggerGraphState::cardRepository) }
|
||||
|
||||
private val onlineCardVerifier: OnlineCardVerifier by lazy { store.inject(DaggerGraphState::onlineCardVerifier) }
|
||||
|
||||
private var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
|
||||
|
||||
suspend fun loadArtworkUrl(): String {
|
||||
val cardInfo = cardInfo
|
||||
?: onlineCardVerifier.getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
|
||||
|
||||
this.cardInfo = cardInfo
|
||||
|
||||
return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo, onlineCardVerifier)
|
||||
}
|
||||
|
||||
suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance {
|
||||
val isDemoCard = scanResponse.isDemoCard()
|
||||
val balance = when (val result = walletManager.safeUpdate(isDemoCard)) {
|
||||
is Result.Success -> {
|
||||
val wallet = walletManager.wallet
|
||||
val valueOfAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
if (valueOfAmount == null) {
|
||||
val customError = TapError.CustomError("Amount is NULL")
|
||||
OnboardingWalletBalance.criticalError(customError)
|
||||
} else {
|
||||
val balance = if (valueOfAmount.isZero()) {
|
||||
OnboardingWalletBalance.done(valueOfAmount, wallet.hasPendingTransactions())
|
||||
} else {
|
||||
OnboardingWalletBalance.done(valueOfAmount, false)
|
||||
}
|
||||
balance
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
val error = result.error as? TapError ?: TapError.UnknownError
|
||||
when (error) {
|
||||
is TapError.WalletManager.NoAccountError -> OnboardingWalletBalance.error(error)
|
||||
// NoInternetConnection, WalletManager.InternalError
|
||||
else -> {
|
||||
Timber.e(error.localizedMessage)
|
||||
OnboardingWalletBalance.criticalError(TapError.WalletManager.BlockchainIsUnreachableTryLater)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return balance.copy(
|
||||
currency = Currency.Blockchain(
|
||||
blockchain = walletManager.wallet.blockchain,
|
||||
derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun startActivation(cardId: String) {
|
||||
cardRepository.startCardActivation(cardId)
|
||||
}
|
||||
|
||||
suspend fun finishActivation(cardId: String) {
|
||||
cardRepository.finishCardActivation(cardId)
|
||||
}
|
||||
|
||||
suspend fun finishActivation(cardIds: List<String>) {
|
||||
cardRepository.finishCardsActivation(cardIds)
|
||||
}
|
||||
|
||||
suspend fun isActivationStarted(cardId: String): Boolean = cardRepository.isActivationStarted(cardId)
|
||||
|
||||
suspend fun isActivationFinished(cardId: String): Boolean = cardRepository.isActivationFinished(cardId)
|
||||
|
||||
suspend fun isActivationInProgress(cardId: String): Boolean = cardRepository.isActivationInProgress(cardId)
|
||||
}
|
||||
|
||||
data class OnboardingWalletBalance(
|
||||
val value: BigDecimal = BigDecimal.ZERO,
|
||||
val currency: Currency = Currency.Blockchain(Blockchain.Unknown, null),
|
||||
val hasIncomingTransaction: Boolean = false,
|
||||
val state: ProgressState,
|
||||
val error: TapError? = null,
|
||||
val criticalError: TapError? = null,
|
||||
) {
|
||||
|
||||
val amountToCreateAccount: String?
|
||||
get() = if (error is TapError.WalletManager.NoAccountError) error.customMessage else null
|
||||
|
||||
fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction
|
||||
|
||||
companion object {
|
||||
fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
state = ProgressState.Error,
|
||||
error = error,
|
||||
)
|
||||
|
||||
fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
state = ProgressState.Error,
|
||||
criticalError = error,
|
||||
)
|
||||
|
||||
fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
value,
|
||||
state = ProgressState.Loading,
|
||||
)
|
||||
|
||||
fun done(value: BigDecimal, hasTransactions: Boolean): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
value,
|
||||
hasIncomingTransaction = hasTransactions,
|
||||
state = ProgressState.Done,
|
||||
)
|
||||
}
|
||||
}
|
||||
class OnboardingManager(var scanResponse: ScanResponse)
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingMenuProvider(
|
||||
private val scanResponseProvider: Provider<ScanResponse>,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_onboarding, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.menu_item_chat_support -> {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
|
||||
val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponseProvider()).getOrNull()
|
||||
?: error("CardInfo must be not null")
|
||||
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||
.invoke(type = FeedbackEmailType.DirectUserRequest(cardInfo))
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.MenuProvider
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.transitions.HomeToOnboardingTransition
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingMainBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseOnboardingFragment<T> : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber<T> {
|
||||
|
||||
protected val binding: FragmentOnboardingMainBinding by viewBinding(FragmentOnboardingMainBinding::bind)
|
||||
protected val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
|
||||
|
||||
override fun configureTransitions() {
|
||||
enterTransition = HomeToOnboardingTransition()
|
||||
exitTransition = HomeToOnboardingTransition()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: store.state.detailsState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
protected fun showConfetti(show: Boolean) = with(binding.vConfetti) {
|
||||
lavConfetti.show(show)
|
||||
|
||||
if (show) {
|
||||
lavConfetti.playAnimation()
|
||||
} else {
|
||||
lavConfetti.cancelAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,228 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.ShareElement
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStep
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Deprecated("Use OnboardingNoteComponent")
|
||||
class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
||||
|
||||
private var previousStep: OnboardingNoteStep = OnboardingNoteStep.None
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
|
||||
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
|
||||
binding.toolbar.setTitle(R.string.onboarding_title)
|
||||
btnRefreshBalanceWidget = RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer)
|
||||
|
||||
store.dispatch(OnboardingNoteAction.Init)
|
||||
store.dispatch(OnboardingNoteAction.LoadCardArtwork)
|
||||
store.dispatch(OnboardingNoteAction.DetermineStepOfScreen)
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.onboardingNoteState == newState.onboardingNoteState
|
||||
}.select { it.onboardingNoteState }
|
||||
}
|
||||
storeSubscribersList.add(this)
|
||||
}
|
||||
|
||||
override fun newState(state: OnboardingNoteState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
|
||||
pbBinding.pbState.max = state.steps.size - 1
|
||||
pbBinding.pbState.progress = state.progress
|
||||
|
||||
when (state.currentStep) {
|
||||
OnboardingNoteStep.None -> setupNoneState()
|
||||
OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state)
|
||||
OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state)
|
||||
OnboardingNoteStep.Done -> setupDoneState(state)
|
||||
}
|
||||
setBalance(state)
|
||||
showConfetti(state.showConfetti)
|
||||
previousStep = state.currentStep
|
||||
}
|
||||
|
||||
private fun setBalance(state: OnboardingNoteState) {
|
||||
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
|
||||
|
||||
with(mainBinding.onboardingTopContainer.onboardingTvBalance) {
|
||||
if (state.balanceCriticalError == null) {
|
||||
val balanceValue = state.walletBalance.value.stripZeroPlainString()
|
||||
tvBalanceValue.text = balanceValue
|
||||
tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
|
||||
} else {
|
||||
tvBalanceValue.text = "–"
|
||||
tvBalanceCurrency.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNoneState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.isVisible = false
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnRefreshBalanceWidget.show(false)
|
||||
tvHeader.isVisible = false
|
||||
tvBody.isVisible = false
|
||||
|
||||
btnMainAction.text = ""
|
||||
btnMainAction.icon = null
|
||||
btnAlternativeAction.text = ""
|
||||
btnAlternativeAction.icon = null
|
||||
tvHeader.text = ""
|
||||
tvBody.text = ""
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingNoteAction.CreateWallet)
|
||||
}
|
||||
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
|
||||
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_circle),
|
||||
)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
|
||||
|
||||
btnAlternativeAction.isVisible = false // temporary
|
||||
}
|
||||
|
||||
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
if (availableForBuy(state.scanResponse, state.walletBalance)) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.TopUp)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = true
|
||||
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
|
||||
btnAlternativeAction.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
|
||||
}
|
||||
} else {
|
||||
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
}
|
||||
|
||||
tvHeader.setText(R.string.onboarding_topup_title)
|
||||
if (state.balanceNonCriticalError == null) {
|
||||
tvBody.setText(R.string.onboarding_top_up_body)
|
||||
} else {
|
||||
state.walletBalance.amountToCreateAccount?.let { amount ->
|
||||
val tvBodyMessage = getString(
|
||||
R.string.onboarding_top_up_body_no_account_error,
|
||||
amount,
|
||||
state.walletBalance.currency.currencySymbol,
|
||||
)
|
||||
tvBody.text = tvBodyMessage
|
||||
}
|
||||
}
|
||||
|
||||
btnRefreshBalanceWidget.changeState(state.walletBalance.state)
|
||||
if (btnRefreshBalanceWidget.isShowing != true) {
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.Balance.Update)
|
||||
}
|
||||
}
|
||||
|
||||
val drawable = requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(drawable)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet)
|
||||
}
|
||||
|
||||
private fun setupDoneState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingNoteAction.Done)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.text = ""
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation)
|
||||
}
|
||||
|
||||
private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) {
|
||||
if (this.previousStep == currentStep) return
|
||||
|
||||
with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean {
|
||||
scanResponse ?: return false
|
||||
return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency)
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(OnboardingNoteAction.OnBackPressed)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingNoteAction : Action {
|
||||
// from user, ui
|
||||
object Init : OnboardingNoteAction()
|
||||
object LoadCardArtwork : OnboardingNoteAction()
|
||||
object CreateWallet : OnboardingNoteAction()
|
||||
object TopUp : OnboardingNoteAction()
|
||||
object ShowAddressInfoDialog : OnboardingNoteAction()
|
||||
object OnBackPressed : OnboardingNoteAction()
|
||||
|
||||
// from redux
|
||||
class SetArtworkUrl(val artworkUrl: String) : OnboardingNoteAction()
|
||||
data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction()
|
||||
object DetermineStepOfScreen : OnboardingNoteAction()
|
||||
object Done : OnboardingNoteAction()
|
||||
|
||||
data class SetStepOfScreen(val step: OnboardingNoteStep) : OnboardingNoteAction()
|
||||
|
||||
sealed class Balance {
|
||||
object Update : OnboardingNoteAction()
|
||||
data class Set(val balance: OnboardingWalletBalance) : OnboardingNoteAction()
|
||||
data class SetCriticalError(val error: TapError?) : OnboardingNoteAction()
|
||||
data class SetNonCriticalError(val error: TapError?) : OnboardingNoteAction()
|
||||
}
|
||||
|
||||
sealed class Confetti {
|
||||
object Show : OnboardingNoteAction()
|
||||
object Hide : OnboardingNoteAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
object OnboardingNoteMiddleware {
|
||||
val handler = onboardingNoteMiddleware
|
||||
}
|
||||
|
||||
private val onboardingNoteMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleNoteAction(state, action, dispatch)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch: DispatchFunction) {
|
||||
if (action !is OnboardingNoteAction) return
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager ?: return
|
||||
|
||||
val scanResponse = onboardingManager.scanResponse
|
||||
val card = onboardingManager.scanResponse.card
|
||||
val noteState = store.state.onboardingNoteState
|
||||
|
||||
when (action) {
|
||||
is OnboardingNoteAction.Init -> {
|
||||
scope.launch {
|
||||
if (!onboardingManager.isActivationStarted(card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.LoadCardArtwork -> {
|
||||
scope.launch {
|
||||
val artworkUrl = onboardingManager.loadArtworkUrl()
|
||||
withMainContext { store.dispatch(OnboardingNoteAction.SetArtworkUrl(artworkUrl)) }
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.DetermineStepOfScreen -> {
|
||||
val step = when {
|
||||
card.wallets.isEmpty() -> OnboardingNoteStep.CreateWallet
|
||||
noteState.walletBalance.balanceIsToppedUp() -> OnboardingNoteStep.Done
|
||||
else -> OnboardingNoteStep.TopUpWallet
|
||||
}
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(step))
|
||||
}
|
||||
is OnboardingNoteAction.SetStepOfScreen -> {
|
||||
when (action.step) {
|
||||
OnboardingNoteStep.CreateWallet -> {
|
||||
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
|
||||
}
|
||||
OnboardingNoteStep.TopUpWallet -> {
|
||||
Analytics.send(Onboarding.Topup.ScreenOpened())
|
||||
store.dispatch(OnboardingNoteAction.Balance.Update)
|
||||
}
|
||||
OnboardingNoteStep.Done -> {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
mainScope.launch {
|
||||
onboardingManager.finishActivation(card.cardId)
|
||||
postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) }
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createProductWallet(scanResponse)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
val updatedResponse = scanResponse.copy(card = result.data.card)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.startActivation(updatedResponse.card.cardId)
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet))
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.Balance.Update -> {
|
||||
val walletManager = if (noteState.walletManager != null) {
|
||||
noteState.walletManager
|
||||
} else {
|
||||
val wmFactory = runBlocking {
|
||||
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
|
||||
}
|
||||
val walletManager = wmFactory?.makePrimaryWalletManager(scanResponse).guard {
|
||||
val message = "Loading cancelled. Cause: wallet manager didn't created"
|
||||
val customError = TapError.CustomError(message)
|
||||
store.dispatchErrorNotification(customError)
|
||||
return
|
||||
}
|
||||
dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
|
||||
walletManager
|
||||
}
|
||||
|
||||
val isLoadedBefore = noteState.walletBalance.state != ProgressState.Loading
|
||||
val balanceIsLoading = noteState.walletBalance.copy(
|
||||
currency = Currency.Blockchain(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
criticalError = null,
|
||||
)
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading))
|
||||
|
||||
scope.launch {
|
||||
val loadedBalance = onboardingManager.updateBalance(walletManager)
|
||||
delay(if (isLoadedBefore) 0 else 300)
|
||||
loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) }
|
||||
withMainContext {
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetCriticalError(loadedBalance.criticalError))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.Balance.Set -> {
|
||||
if (action.balance.balanceIsToppedUp()) {
|
||||
OnboardingHelper.sendToppedUpEvent(scanResponse)
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done))
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.ShowAddressInfoDialog -> {
|
||||
val addressData = noteState.walletManager?.getAddressData() ?: return
|
||||
|
||||
Analytics.send(Onboarding.Topup.ButtonShowWalletAddress())
|
||||
val appDialog = AppDialog.AddressInfoDialog(noteState.walletBalance.currency, addressData)
|
||||
store.dispatchDialogShow(appDialog)
|
||||
}
|
||||
is OnboardingNoteAction.TopUp -> {
|
||||
val walletManager = noteState.walletManager.guard {
|
||||
store.dispatchDebugErrorNotification("NPE: WalletManager")
|
||||
return
|
||||
}
|
||||
|
||||
OnboardingHelper.handleTopUpAction(
|
||||
walletManager = walletManager,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
is OnboardingNoteAction.Done -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
}
|
||||
is OnboardingNoteAction.OnBackPressed -> {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.InterruptOnboarding(
|
||||
onOk = {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import org.rekotlin.Action
|
||||
|
||||
object OnboardingNoteReducer {
|
||||
fun reduce(action: Action, state: AppState): OnboardingNoteState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, appState: AppState): OnboardingNoteState {
|
||||
var state = appState.onboardingNoteState
|
||||
|
||||
when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
state = OnboardingNoteState(scanResponse = action.scanResponse)
|
||||
}
|
||||
is OnboardingNoteAction.SetArtworkUrl -> {
|
||||
state = state.copy(cardArtworkUrl = action.artworkUrl)
|
||||
}
|
||||
is OnboardingNoteAction.SetWalletManager -> {
|
||||
state = state.copy(walletManager = action.walletManager)
|
||||
}
|
||||
is OnboardingNoteAction.Balance.Set -> {
|
||||
state = state.copy(walletBalance = action.balance)
|
||||
}
|
||||
is OnboardingNoteAction.Balance.SetCriticalError -> {
|
||||
state = state.copy(balanceCriticalError = action.error)
|
||||
}
|
||||
is OnboardingNoteAction.Balance.SetNonCriticalError -> {
|
||||
state = state.copy(balanceNonCriticalError = action.error)
|
||||
}
|
||||
is OnboardingNoteAction.SetStepOfScreen -> {
|
||||
if (action.step != state.currentStep && state.steps.contains(action.step)) {
|
||||
state = state.copy(currentStep = action.step)
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.Confetti.Show -> {
|
||||
state = state.copy(showConfetti = true)
|
||||
}
|
||||
is OnboardingNoteAction.Confetti.Hide -> {
|
||||
state = state.copy(showConfetti = false)
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingNoteState(
|
||||
val walletManager: WalletManager? = null,
|
||||
// UI
|
||||
val cardArtworkUrl: String? = null,
|
||||
val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(),
|
||||
val balanceNonCriticalError: TapError? = null,
|
||||
val balanceCriticalError: TapError? = null,
|
||||
val currentStep: OnboardingNoteStep = OnboardingNoteStep.None,
|
||||
val steps: List<OnboardingNoteStep> = OnboardingNoteStep.values().toList(),
|
||||
val showConfetti: Boolean = false,
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : StateType {
|
||||
|
||||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
}
|
||||
|
||||
enum class OnboardingNoteStep {
|
||||
None, CreateWallet, TopUpWallet, Done
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.ShareElement
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsStep
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCardsState>() {
|
||||
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
|
||||
binding.toolbar.setTitle(R.string.onboarding_title)
|
||||
store.dispatch(OnboardingOtherCardsAction.Init)
|
||||
store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork)
|
||||
store.dispatch(OnboardingOtherCardsAction.DetermineStepOfScreen)
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.onboardingOtherCardsState == newState.onboardingOtherCardsState
|
||||
}.select { it.onboardingOtherCardsState }
|
||||
}
|
||||
storeSubscribersList.add(this)
|
||||
}
|
||||
|
||||
override fun newState(state: OnboardingOtherCardsState) {
|
||||
if (activity == null || view == null) return
|
||||
if (state.currentStep == OnboardingOtherCardsStep.None) return
|
||||
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
|
||||
pbBinding.pbState.max = state.steps.size - 1
|
||||
pbBinding.pbState.progress = state.progress
|
||||
|
||||
when (state.currentStep) {
|
||||
OnboardingOtherCardsStep.CreateWallet -> setupCreateWalletState()
|
||||
OnboardingOtherCardsStep.Done -> setupDoneState()
|
||||
else -> {}
|
||||
}
|
||||
showConfetti(state.showConfetti)
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingOtherCardsAction.CreateWallet)
|
||||
}
|
||||
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
btnAlternativeAction.isVisible = false // temporary
|
||||
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_circle),
|
||||
)
|
||||
updateConstraints(R.layout.lp_onboarding_create_wallet)
|
||||
}
|
||||
|
||||
private fun setupDoneState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingOtherCardsAction.Done)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.text = ""
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
updateConstraints(R.layout.lp_onboarding_done)
|
||||
}
|
||||
|
||||
private fun updateConstraints(@LayoutRes layoutId: Int) = with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(OnboardingOtherCardsAction.OnBackPressed)
|
||||
super.handleOnBackPressed()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingOtherCardsAction : Action {
|
||||
// from user, ui
|
||||
object Init : OnboardingOtherCardsAction()
|
||||
object LoadCardArtwork : OnboardingOtherCardsAction()
|
||||
object CreateWallet : OnboardingOtherCardsAction()
|
||||
|
||||
// from redux
|
||||
class SetArtworkUrl(val artworkUrl: String) : OnboardingOtherCardsAction()
|
||||
object DetermineStepOfScreen : OnboardingOtherCardsAction()
|
||||
object Done : OnboardingOtherCardsAction()
|
||||
object OnBackPressed : OnboardingOtherCardsAction()
|
||||
|
||||
data class SetStepOfScreen(val step: OnboardingOtherCardsStep) : OnboardingOtherCardsAction()
|
||||
|
||||
sealed class Confetti {
|
||||
object Show : OnboardingOtherCardsAction()
|
||||
object Hide : OnboardingOtherCardsAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
object OnboardingOtherCardsMiddleware {
|
||||
val handler = onboardingOtherCardsMiddleware
|
||||
}
|
||||
|
||||
private val onboardingOtherCardsMiddleware: Middleware<AppState> = { dispatch, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleOtherCardsAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleOtherCardsAction(action: Action) {
|
||||
if (action !is OnboardingOtherCardsAction) return
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager ?: return
|
||||
|
||||
val card = onboardingManager.scanResponse.card
|
||||
|
||||
when (action) {
|
||||
is OnboardingOtherCardsAction.Init -> {
|
||||
scope.launch {
|
||||
if (!onboardingManager.isActivationStarted(card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.LoadCardArtwork -> {
|
||||
scope.launch {
|
||||
val artworkUrl = onboardingManager.loadArtworkUrl()
|
||||
withMainContext { store.dispatch(OnboardingOtherCardsAction.SetArtworkUrl(artworkUrl)) }
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.DetermineStepOfScreen -> {
|
||||
val step = when {
|
||||
card.wallets.isEmpty() -> OnboardingOtherCardsStep.CreateWallet
|
||||
else -> OnboardingOtherCardsStep.Done
|
||||
}
|
||||
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(step))
|
||||
}
|
||||
is OnboardingOtherCardsAction.SetStepOfScreen -> {
|
||||
when (action.step) {
|
||||
OnboardingOtherCardsStep.CreateWallet -> {
|
||||
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
|
||||
}
|
||||
OnboardingOtherCardsStep.Done -> {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
mainScope.launch {
|
||||
onboardingManager.finishActivation(card.cardId)
|
||||
postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) }
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createProductWallet(
|
||||
scanResponse = onboardingManager.scanResponse,
|
||||
shouldReset = globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
val updatedResponse = onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
)
|
||||
val updatedCard = updatedResponse.card
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.startActivation(updatedCard.cardId)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OnboardingOtherCardsAction.Done -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(onboardingManager.scanResponse)
|
||||
}
|
||||
OnboardingOtherCardsAction.OnBackPressed -> {
|
||||
OnboardingHelper.onInterrupted()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleActivationError() {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.WalletActivationError(
|
||||
onConfirm = {
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(true))
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import org.rekotlin.Action
|
||||
|
||||
object OnboardingOtherCardsReducer {
|
||||
fun reduce(action: Action, state: AppState): OnboardingOtherCardsState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, appState: AppState): OnboardingOtherCardsState {
|
||||
var state = appState.onboardingOtherCardsState
|
||||
|
||||
when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
state = OnboardingOtherCardsState()
|
||||
}
|
||||
is OnboardingOtherCardsAction.SetArtworkUrl -> {
|
||||
state = state.copy(cardArtworkUrl = action.artworkUrl)
|
||||
}
|
||||
is OnboardingOtherCardsAction.SetStepOfScreen -> {
|
||||
if (action.step != state.currentStep && state.steps.contains(action.step)) {
|
||||
state = state.copy(currentStep = action.step)
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.Confetti.Show -> {
|
||||
state = state.copy(showConfetti = true)
|
||||
}
|
||||
is OnboardingOtherCardsAction.Confetti.Hide -> {
|
||||
state = state.copy(showConfetti = false)
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingOtherCardsState(
|
||||
// UI
|
||||
val cardArtworkUrl: String? = null,
|
||||
val showConfetti: Boolean = false,
|
||||
val currentStep: OnboardingOtherCardsStep = OnboardingOtherCardsStep.None,
|
||||
val steps: List<OnboardingOtherCardsStep> = OnboardingOtherCardsStep.values().toList(),
|
||||
) : StateType {
|
||||
|
||||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
}
|
||||
|
||||
enum class OnboardingOtherCardsStep {
|
||||
None, CreateWallet, Done
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class TwinCardsAction : Action {
|
||||
data class IfTwinsPrepareState(val scanResponse: ScanResponse) : TwinCardsAction()
|
||||
|
||||
object Init : TwinCardsAction()
|
||||
data class SetMode(val mode: CreateTwinWalletMode) : TwinCardsAction()
|
||||
data class SetStepOfScreen(val step: TwinCardsStep) : TwinCardsAction()
|
||||
data class SetUserUnderstand(val isUnderstand: Boolean) : TwinCardsAction()
|
||||
|
||||
sealed class CardsManager {
|
||||
data class Set(val manager: TwinCardsManager) : TwinCardsAction()
|
||||
object Release : TwinCardsAction()
|
||||
}
|
||||
|
||||
sealed class Wallet : TwinCardsAction() {
|
||||
data class LaunchFirstStep(val initialMessage: Message) : TwinCardsAction()
|
||||
data class LaunchSecondStep(
|
||||
val initialMessage: Message,
|
||||
val preparingMessage: Message,
|
||||
val creatingWalletMessage: Message,
|
||||
) : TwinCardsAction()
|
||||
|
||||
data class LaunchThirdStep(val message: Message) : TwinCardsAction()
|
||||
}
|
||||
|
||||
// for the onboarding
|
||||
data class SetPairCardId(val cardId: String) : TwinCardsAction()
|
||||
object TopUp : TwinCardsAction()
|
||||
object ShowAddressInfoDialog : TwinCardsAction()
|
||||
data class SetWalletManager(val walletManager: WalletManager) : TwinCardsAction()
|
||||
object Done : TwinCardsAction()
|
||||
|
||||
data class OnBackPressed(
|
||||
// this is necessary for the correct animation of the return of cards to the Home screen
|
||||
val shouldResetTwinCardsWidget: (should: Boolean, popAction: VoidCallback) -> Unit,
|
||||
) : TwinCardsAction()
|
||||
|
||||
data class SaveScannedTwinCardAndNavigateToWallet(
|
||||
val scanResponse: ScanResponse,
|
||||
) : TwinCardsAction()
|
||||
|
||||
sealed class Balance {
|
||||
object Update : TwinCardsAction()
|
||||
data class Set(val balance: OnboardingWalletBalance) : TwinCardsAction()
|
||||
data class SetCriticalError(val error: TapError?) : TwinCardsAction()
|
||||
data class SetNonCriticalError(val error: TapError?) : TwinCardsAction()
|
||||
}
|
||||
|
||||
sealed class Confetti {
|
||||
object Show : TwinCardsAction()
|
||||
object Hide : TwinCardsAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,399 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
import org.rekotlin.Middleware
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object TwinCardsMiddleware {
|
||||
val handler = twinsWalletMiddleware
|
||||
}
|
||||
|
||||
private val twinsWalletMiddleware: Middleware<AppState> = { dispatch, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handle(action, dispatch)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handle(action: Action, dispatch: DispatchFunction) {
|
||||
if (action !is TwinCardsAction) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
val twinCardsState = store.state.twinCardsState
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
fun getScanResponse(): ScanResponse {
|
||||
return when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse
|
||||
is CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse
|
||||
} ?: throw NullPointerException("ScanResponse can't be NULL")
|
||||
}
|
||||
|
||||
fun updateScanResponse(response: ScanResponse) {
|
||||
when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
|
||||
is CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response))
|
||||
}
|
||||
}
|
||||
|
||||
fun startCardActivation(cardId: String) {
|
||||
if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
|
||||
scope.launch {
|
||||
onboardingManager?.startActivation(cardId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun finishCardActivation() {
|
||||
if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
val cardIds = listOfNotNull(getScanResponse().card.cardId, twinCardsState.pairCardId)
|
||||
|
||||
if (cardIds.isNotEmpty()) {
|
||||
mainScope.launch {
|
||||
onboardingManager?.finishActivation(cardIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (action) {
|
||||
is TwinCardsAction.Init -> {
|
||||
mainScope.launch {
|
||||
if (twinCardsState.currentStep is TwinCardsStep.WelcomeOnly) return@launch
|
||||
|
||||
if (twinCardsState.mode is CreateTwinWalletMode.RecreateWallet) {
|
||||
store.dispatch(GlobalAction.SaveScanResponse(twinCardsState.mode.scanResponse))
|
||||
}
|
||||
|
||||
val scanResponse = getScanResponse()
|
||||
onboardingManager?.apply {
|
||||
if (!isActivationStarted(scanResponse.card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
|
||||
when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
mainScope.launch {
|
||||
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
|
||||
.invokeSync()
|
||||
|
||||
val dispatchAction = if (wasTwinsOnboardingShown) {
|
||||
val step = when {
|
||||
!scanResponse.twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet
|
||||
twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done
|
||||
else -> TwinCardsStep.TopUpWallet
|
||||
}
|
||||
TwinCardsAction.SetStepOfScreen(step)
|
||||
} else {
|
||||
TwinCardsAction.SetStepOfScreen(TwinCardsStep.Welcome)
|
||||
}
|
||||
|
||||
store.dispatch(dispatchAction)
|
||||
}
|
||||
}
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.SetStepOfScreen -> {
|
||||
when (action.step) {
|
||||
is TwinCardsStep.WelcomeOnly, TwinCardsStep.Welcome -> {
|
||||
Analytics.send(Onboarding.Twins.ScreenOpened())
|
||||
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::saveTwinsOnboardingShownUseCase).invoke()
|
||||
}
|
||||
}
|
||||
is TwinCardsStep.CreateFirstWallet -> {
|
||||
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
|
||||
Analytics.send(Onboarding.Twins.SetupStarted())
|
||||
}
|
||||
TwinCardsStep.TopUpWallet -> {
|
||||
Analytics.send(Onboarding.Topup.ScreenOpened())
|
||||
store.dispatch(TwinCardsAction.Balance.Update)
|
||||
}
|
||||
TwinCardsStep.Done -> {
|
||||
finishCardActivation()
|
||||
postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) }
|
||||
}
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.Warning,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
TwinCardsStep.CreateThirdWallet,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Wallet.LaunchFirstStep -> {
|
||||
val manager = TwinCardsManager(card = getScanResponse().card)
|
||||
store.dispatch(TwinCardsAction.CardsManager.Set(manager))
|
||||
|
||||
scope.launch {
|
||||
when (val result = manager.createFirstWallet(action.initialMessage)) {
|
||||
is CompletionResult.Success -> {
|
||||
// remove wallet only after first step of retwin
|
||||
scope.launch {
|
||||
userWalletsListManager.delete(
|
||||
listOfNotNull(UserWalletIdBuilder.scanResponse(getScanResponse()).build()),
|
||||
)
|
||||
}
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
startCardActivation(result.data.cardId)
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateSecondWallet))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Wallet.LaunchSecondStep -> {
|
||||
val manager = twinCardsState.twinCardsManager ?: return
|
||||
|
||||
scope.launch {
|
||||
val result = manager.createSecondWallet(
|
||||
action.initialMessage,
|
||||
action.preparingMessage,
|
||||
action.creatingWalletMessage,
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
startCardActivation(result.data.cardId)
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
store.dispatch(TwinCardsAction.SetPairCardId(result.data.cardId))
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateThirdWallet))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Wallet.LaunchThirdStep -> {
|
||||
val manager = twinCardsState.twinCardsManager ?: return
|
||||
|
||||
scope.launch {
|
||||
when (val result = manager.complete(action.message)) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.Twins.SetupFinished())
|
||||
updateScanResponse(result.data)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet))
|
||||
}
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Balance.Update -> {
|
||||
val walletManager = if (twinCardsState.walletManager != null) {
|
||||
twinCardsState.walletManager
|
||||
} else {
|
||||
val wmFactory = runBlocking {
|
||||
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
|
||||
}
|
||||
val walletManager = wmFactory?.makePrimaryWalletManager(getScanResponse()).guard {
|
||||
val message = "Loading cancelled. Cause: wallet manager didn't created"
|
||||
val customError = TapError.CustomError(message)
|
||||
store.dispatchErrorNotification(customError)
|
||||
return
|
||||
}
|
||||
dispatch(TwinCardsAction.SetWalletManager(walletManager))
|
||||
walletManager
|
||||
}
|
||||
val isLoadedBefore = twinCardsState.walletBalance.state != ProgressState.Loading
|
||||
val balanceIsLoading = twinCardsState.walletBalance.copy(
|
||||
currency = Currency.Blockchain(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
criticalError = null,
|
||||
)
|
||||
store.dispatch(TwinCardsAction.Balance.Set(balanceIsLoading))
|
||||
|
||||
scope.launch {
|
||||
val loadedBalance = onboardingManager?.updateBalance(walletManager) ?: return@launch
|
||||
loadedBalance.criticalError?.let(store::dispatchErrorNotification)
|
||||
delay(if (isLoadedBefore) 0 else 300)
|
||||
withMainContext {
|
||||
store.dispatch(TwinCardsAction.Balance.Set(loadedBalance))
|
||||
store.dispatch(TwinCardsAction.Balance.SetCriticalError(loadedBalance.criticalError))
|
||||
store.dispatch(TwinCardsAction.Balance.SetNonCriticalError(loadedBalance.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Balance.Set -> {
|
||||
if (action.balance.balanceIsToppedUp()) {
|
||||
OnboardingHelper.sendToppedUpEvent(getScanResponse())
|
||||
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done))
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.ShowAddressInfoDialog -> {
|
||||
val addressData = twinCardsState.walletManager?.getAddressData() ?: return
|
||||
|
||||
val appDialog = AppDialog.AddressInfoDialog(twinCardsState.walletBalance.currency, addressData)
|
||||
store.dispatchDialogShow(appDialog)
|
||||
}
|
||||
is TwinCardsAction.TopUp -> {
|
||||
val walletManager = twinCardsState.walletManager.guard {
|
||||
store.dispatchDebugErrorNotification("NPE: WalletManager")
|
||||
return
|
||||
}
|
||||
|
||||
OnboardingHelper.handleTopUpAction(
|
||||
walletManager = walletManager,
|
||||
scanResponse = onboardingManager?.scanResponse ?: return,
|
||||
)
|
||||
}
|
||||
TwinCardsAction.Done -> {
|
||||
val scanResponse = getScanResponse()
|
||||
when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
store.dispatchOnMain(GlobalAction.Onboarding.Stop)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
}
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
scope.launch {
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
if (walletsRepository.shouldSaveUserWalletsSync()) {
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
} else {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.SaveScannedTwinCardAndNavigateToWallet -> {
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is TwinCardsAction.OnBackPressed -> {
|
||||
if (twinCardsState.twinningInProgress) {
|
||||
store.dispatchDialogShow(OnboardingDialog.TwinningProcessNotCompleted)
|
||||
} else {
|
||||
val onOkCallback = {
|
||||
val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet &&
|
||||
twinCardsState.currentStep != TwinCardsStep.TopUpWallet &&
|
||||
twinCardsState.currentStep != TwinCardsStep.Done
|
||||
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(TwinCardsAction.CardsManager.Release)
|
||||
|
||||
action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
|
||||
store.dispatchNavigationAction { popTo(routeClass = getPopBackScreen()) }
|
||||
}
|
||||
}
|
||||
store.dispatchDialogShow(OnboardingDialog.InterruptOnboarding(onOkCallback))
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCardVerificationFailedDialog(error: TangemError) {
|
||||
if (error is TangemSdkError.CardVerificationFailed) {
|
||||
Analytics.send(
|
||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(
|
||||
AnalyticsParam.ScreensSources.Onboarding,
|
||||
),
|
||||
)
|
||||
|
||||
val resource = error.localizedDescriptionRes()
|
||||
val resId = resource.resId ?: R.string.common_unknown_error
|
||||
val resArgs = resource.args.map { it.value }
|
||||
|
||||
store.inject(DaggerGraphState::uiMessageSender).send(
|
||||
message = Dialogs.cardVerificationFailed(
|
||||
errorDescription = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
onRequestSupport = {
|
||||
mainScope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke(
|
||||
type = FeedbackEmailType.CardAttestationFailed,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPopBackScreen(): KClass<out AppRoute> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
return if (userWalletsListManager.hasUserWallets) {
|
||||
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync!! }.getOrElse { false }
|
||||
|
||||
if (isLocked) {
|
||||
AppRoute.Welcome::class
|
||||
} else {
|
||||
AppRoute.Wallet::class
|
||||
}
|
||||
} else {
|
||||
AppRoute.Home::class
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.domain.common.getTwinCardNumber
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
object TwinCardsReducer {
|
||||
fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun internalReduce(action: Action, state: AppState): TwinCardsState {
|
||||
if (action !is TwinCardsAction) return state.twinCardsState
|
||||
|
||||
var state = state.twinCardsState
|
||||
|
||||
when (action) {
|
||||
is TwinCardsAction.IfTwinsPrepareState -> {
|
||||
state = if (action.scanResponse.cardTypesResolver.isTangemTwins()) {
|
||||
val cardNumber = action.scanResponse.card.getTwinCardNumber() ?: return state
|
||||
|
||||
TwinCardsState(
|
||||
cardNumber = cardNumber,
|
||||
mode = CreateTwinWalletMode.CreateWallet,
|
||||
)
|
||||
} else {
|
||||
TwinCardsState()
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.SetMode -> {
|
||||
state = state.copy(
|
||||
mode = action.mode,
|
||||
userWasUnderstandIfWalletRecreate = false,
|
||||
)
|
||||
}
|
||||
is TwinCardsAction.SetStepOfScreen -> {
|
||||
state = state.copy(
|
||||
currentStep = action.step,
|
||||
welcomeOnlyScanResponse = (action.step as? TwinCardsStep.WelcomeOnly)?.scanResponse,
|
||||
)
|
||||
}
|
||||
is TwinCardsAction.SetUserUnderstand -> {
|
||||
state = state.copy(userWasUnderstandIfWalletRecreate = action.isUnderstand)
|
||||
}
|
||||
is TwinCardsAction.CardsManager.Set -> {
|
||||
state = state.copy(twinCardsManager = action.manager)
|
||||
}
|
||||
TwinCardsAction.CardsManager.Release -> {
|
||||
state = state.copy(twinCardsManager = null)
|
||||
}
|
||||
is TwinCardsAction.SetPairCardId -> {
|
||||
state = state.copy(pairCardId = action.cardId)
|
||||
}
|
||||
is TwinCardsAction.SetWalletManager -> {
|
||||
state = state.copy(walletManager = action.walletManager)
|
||||
}
|
||||
is TwinCardsAction.Balance.Set -> {
|
||||
state = state.copy(walletBalance = action.balance)
|
||||
}
|
||||
is TwinCardsAction.Balance.SetCriticalError -> {
|
||||
state = state.copy(balanceCriticalError = action.error)
|
||||
}
|
||||
is TwinCardsAction.Balance.SetNonCriticalError -> {
|
||||
state = state.copy(balanceNonCriticalError = action.error)
|
||||
}
|
||||
is TwinCardsAction.Confetti.Show -> {
|
||||
state = state.copy(showConfetti = true)
|
||||
}
|
||||
is TwinCardsAction.Confetti.Hide -> {
|
||||
state = state.copy(showConfetti = false)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class TwinCardsState(
|
||||
// if cardNumber = null -> the scanned card is not Twin
|
||||
val cardNumber: TwinCardNumber? = null,
|
||||
val mode: CreateTwinWalletMode = CreateTwinWalletMode.CreateWallet,
|
||||
val currentStep: TwinCardsStep = TwinCardsStep.Welcome,
|
||||
val userWasUnderstandIfWalletRecreate: Boolean = false,
|
||||
// available only when creating or recreating wallets
|
||||
val twinCardsManager: TwinCardsManager? = null,
|
||||
|
||||
// for the onboarding
|
||||
val pairCardId: String? = null, // available after create\recreate wallets
|
||||
val walletManager: WalletManager? = null,
|
||||
val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(),
|
||||
val balanceNonCriticalError: TapError? = null,
|
||||
val balanceCriticalError: TapError? = null,
|
||||
val showConfetti: Boolean = false,
|
||||
val welcomeOnlyScanResponse: ScanResponse? = null,
|
||||
) : StateType {
|
||||
|
||||
val steps: List<TwinCardsStep>
|
||||
get() = when (mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> listOf(
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.CreateFirstWallet,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
TwinCardsStep.CreateThirdWallet,
|
||||
TwinCardsStep.TopUpWallet,
|
||||
TwinCardsStep.Done,
|
||||
)
|
||||
is CreateTwinWalletMode.RecreateWallet -> listOf(
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.CreateFirstWallet,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
TwinCardsStep.CreateThirdWallet,
|
||||
TwinCardsStep.Done,
|
||||
)
|
||||
}
|
||||
|
||||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
|
||||
val twinningInProgress: Boolean
|
||||
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
|
||||
}
|
||||
|
||||
sealed class CreateTwinWalletMode {
|
||||
data object CreateWallet : CreateTwinWalletMode()
|
||||
|
||||
data class RecreateWallet(
|
||||
val scanResponse: ScanResponse,
|
||||
) : CreateTwinWalletMode()
|
||||
}
|
||||
|
||||
sealed class TwinCardsStep {
|
||||
object None : TwinCardsStep()
|
||||
data class WelcomeOnly(
|
||||
val scanResponse: ScanResponse,
|
||||
) : TwinCardsStep()
|
||||
|
||||
object Welcome : TwinCardsStep()
|
||||
object Warning : TwinCardsStep()
|
||||
object CreateFirstWallet : TwinCardsStep()
|
||||
object CreateSecondWallet : TwinCardsStep()
|
||||
object CreateThirdWallet : TwinCardsStep()
|
||||
|
||||
// for the onboarding
|
||||
object TopUpWallet : TwinCardsStep()
|
||||
object Done : TwinCardsStep()
|
||||
}
|
||||
|
|
@ -1,460 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.ShareElement
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.domain.twins.TwinsCardWidget
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@AndroidEntryPoint
|
||||
internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
private var previousStep: TwinCardsStep = TwinCardsStep.None
|
||||
|
||||
private lateinit var twinsWidget: TwinsCardWidget
|
||||
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
store.dispatch(TwinCardsAction.Init)
|
||||
}
|
||||
|
||||
override fun configureTransitions() {
|
||||
when (store.state.twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
super.configureTransitions()
|
||||
}
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
configureDefaultTransactions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider { getActualScanResponse() },
|
||||
)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
reconfigureLayoutForTwins(mainBinding.onboardingTopContainer)
|
||||
|
||||
val typedValue = TypedValue()
|
||||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
val deviceScaleFactorForWelcomeState = typedValue.float
|
||||
|
||||
twinsWidget = TwinsCardWidget(
|
||||
LeapfrogWidget(mainBinding.onboardingTopContainer.cardsContainer),
|
||||
deviceScaleFactorForWelcomeState,
|
||||
) {
|
||||
285f * deviceScaleFactorForWelcomeState
|
||||
}
|
||||
btnRefreshBalanceWidget =
|
||||
RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer)
|
||||
|
||||
binding.toolbar.title = getText(R.string.twins_recreate_toolbar)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1_URL) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
|
||||
mainBinding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2_URL) {
|
||||
placeholder(R.drawable.card_placeholder_white)
|
||||
error(R.drawable.card_placeholder_white)
|
||||
fallback(R.drawable.card_placeholder_white)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setStatusBarColor(R.color.background_primary)
|
||||
}
|
||||
|
||||
private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) =
|
||||
with(containerBinding) {
|
||||
imvFrontCard.hide()
|
||||
imvCardBackground.hide()
|
||||
cardsContainer.show()
|
||||
|
||||
imvTwinFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
imvTwinBackCard.transitionName = ShareElement.imvBackCard
|
||||
|
||||
// if don't this, the bg_circle_... is overflow the app_bar
|
||||
binding.appBar.bringToFront()
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.twinCardsState == newState.twinCardsState
|
||||
}.select { it.twinCardsState }
|
||||
}
|
||||
storeSubscribersList.add(this)
|
||||
}
|
||||
|
||||
override fun newState(state: TwinCardsState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
pbBinding.pbState.max = state.steps.size - 1
|
||||
pbBinding.pbState.progress = state.progress
|
||||
|
||||
when (state.currentStep) {
|
||||
is TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state, state.currentStep.scanResponse)
|
||||
is TwinCardsStep.Welcome -> setupWelcomeState(state)
|
||||
is TwinCardsStep.Warning -> setupWarningState(state)
|
||||
is TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state)
|
||||
is TwinCardsStep.CreateSecondWallet -> setupCreateSecondWalletState(state)
|
||||
is TwinCardsStep.CreateThirdWallet -> setupCreateThirdWalletState(state)
|
||||
is TwinCardsStep.TopUpWallet -> setupTopUpWalletState(state)
|
||||
is TwinCardsStep.Done -> setupDoneState(state)
|
||||
else -> {}
|
||||
}
|
||||
|
||||
setBalance(state)
|
||||
showConfetti(state.showConfetti)
|
||||
previousStep = state.currentStep
|
||||
}
|
||||
|
||||
private fun setBalance(state: TwinCardsState) {
|
||||
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
|
||||
|
||||
with(mainBinding.onboardingTopContainer.onboardingTvBalance) {
|
||||
if (state.balanceCriticalError == null) {
|
||||
val balanceValue = state.walletBalance.value.stripZeroPlainString()
|
||||
tvBalanceValue.text = balanceValue
|
||||
tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
|
||||
} else {
|
||||
tvBalanceValue.text = "–"
|
||||
tvBalanceCurrency.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWelcomeOnlyState(state: TwinCardsState, scanResponse: ScanResponse) {
|
||||
setupWelcomeState(state) {
|
||||
store.dispatch(TwinCardsAction.SaveScannedTwinCardAndNavigateToWallet(scanResponse))
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.None))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWelcomeState(state: TwinCardsState) {
|
||||
setupWelcomeState(state) {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWelcomeState(state: TwinCardsState, onMainButtonClick: VoidCallback) =
|
||||
with(mainBinding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false)
|
||||
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.show()
|
||||
pbBinding.pbState.hide()
|
||||
|
||||
tvHeader.setText(R.string.twins_onboarding_subtitle)
|
||||
tvBody.text = getString(
|
||||
R.string.twins_onboarding_description_format,
|
||||
state.cardNumber?.pairIndexNumber(),
|
||||
)
|
||||
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener { onMainButtonClick() }
|
||||
}
|
||||
|
||||
private fun setupWarningState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false)
|
||||
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
|
||||
pbBinding.pbState.hide()
|
||||
chbUnderstand.show()
|
||||
|
||||
tvHeader.setText(R.string.common_warning)
|
||||
tvBody.setText(R.string.twins_recreate_warning)
|
||||
|
||||
chbUnderstand.setOnCheckedChangeListener { _, isChecked ->
|
||||
store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked))
|
||||
}
|
||||
btnMainAction.isEnabled = state.userWasUnderstandIfWalletRecreate
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateFirstWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide()
|
||||
chbUnderstand.hide()
|
||||
pbBinding.pbState.show()
|
||||
|
||||
mainBinding.onboardingTopContainer.onboardingWalletContainer.beginDelayedTransition()
|
||||
|
||||
when (previousStep) {
|
||||
TwinCardsStep.None -> {
|
||||
twinsWidget.toLeapfrog(false) {
|
||||
when (state.cardNumber) {
|
||||
TwinCardNumber.First -> {}
|
||||
TwinCardNumber.Second -> {
|
||||
switchToCard(
|
||||
state.cardNumber,
|
||||
false,
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
|
||||
twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
val twinIndexNumber = state.cardNumber?.indexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchFirstStep(
|
||||
initialMessage = Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.isClickable = false
|
||||
}
|
||||
|
||||
private fun setupCreateSecondWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
switchToCard(state.cardNumber?.pairNumber())
|
||||
|
||||
val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text =
|
||||
getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchSecondStep(
|
||||
Message(
|
||||
getString(
|
||||
R.string.twins_recreate_title_format,
|
||||
twinPairIndexNumber,
|
||||
),
|
||||
),
|
||||
Message(getString(R.string.twins_recreate_title_preparing)),
|
||||
Message(getString(R.string.twins_recreate_title_creating_wallet)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateThirdWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
switchToCard(state.cardNumber)
|
||||
|
||||
val twinIndexNumber = state.cardNumber?.indexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchThirdStep(
|
||||
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTopUpWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
when (previousStep) {
|
||||
TwinCardsStep.None, TwinCardsStep.Welcome -> {
|
||||
when (state.cardNumber) {
|
||||
TwinCardNumber.First -> {
|
||||
twinsWidget.leapfrogWidget.unfold(true) {
|
||||
twinsWidget.toActivate(true)
|
||||
}
|
||||
}
|
||||
TwinCardNumber.Second -> {
|
||||
switchToCard(state.cardNumber, true) {
|
||||
twinsWidget.toActivate(true)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
TwinCardsStep.CreateThirdWallet -> {
|
||||
twinsWidget.toActivate()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
if (availableForBuy(getActualScanResponse(), state.walletBalance)) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.TopUp)
|
||||
}
|
||||
btnAlternativeAction.isVisible = true
|
||||
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
|
||||
btnAlternativeAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
} else {
|
||||
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
}
|
||||
|
||||
tvHeader.setText(R.string.onboarding_topup_title)
|
||||
tvBody.setText(R.string.onboarding_top_up_body)
|
||||
btnMainAction.icon = null
|
||||
|
||||
btnRefreshBalanceWidget.changeState(state.walletBalance.state)
|
||||
if (btnRefreshBalanceWidget.isShowing != true) {
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Balance.Update)
|
||||
}
|
||||
}
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.backgroundTintList =
|
||||
requireContext().resources.getColorStateList(R.color.onboarding_card_background, null)
|
||||
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
|
||||
}
|
||||
|
||||
private fun setupDoneState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
store.dispatch(TwinCardsAction.Done)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.isClickable = false
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
val layout = when (state.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
|
||||
is CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
|
||||
}
|
||||
updateConstraints(state.currentStep, layout)
|
||||
}
|
||||
|
||||
private fun updateConstraints(currentStep: TwinCardsStep, @LayoutRes layoutId: Int) {
|
||||
if (this.previousStep == currentStep) return
|
||||
|
||||
with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToCard(cardNumber: TwinCardNumber?, animate: Boolean = true, onEnd: VoidCallback = {}) {
|
||||
val position = cardNumber?.number ?: return
|
||||
|
||||
if (twinsWidget.leapfrogWidget.getViewPositionByIndex(1) != position - 1) {
|
||||
twinsWidget.leapfrogWidget.leap(animate, onEnd)
|
||||
} else {
|
||||
onEnd()
|
||||
}
|
||||
}
|
||||
|
||||
private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean {
|
||||
scanResponse ?: return false
|
||||
return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency)
|
||||
}
|
||||
|
||||
private fun getActualScanResponse(): ScanResponse {
|
||||
return store.state.twinCardsState.welcomeOnlyScanResponse
|
||||
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: store.state.detailsState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(
|
||||
TwinCardsAction.OnBackPressed { should, popAction ->
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
showConfetti(false)
|
||||
if (should) {
|
||||
switchToCard(TwinCardNumber.First, true, popAction)
|
||||
} else {
|
||||
popAction()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TwinCardNumber?.indexNumber(): String {
|
||||
return this?.number?.toString() ?: ""
|
||||
}
|
||||
|
||||
private fun TwinCardNumber?.pairIndexNumber(): String {
|
||||
return this?.pairNumber()?.number?.toString() ?: ""
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.ui.dialog
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object TwinningProcessNotCompletedDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog)
|
||||
.setMessage(R.string.onboarding_twin_exit_warning)
|
||||
.setPositiveButton(R.string.warning_button_ok) { _, _ -> }
|
||||
.setOnDismissListener { store.dispatchDialogHide() }
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
|
||||
sealed class BackupDialog : StateDialog {
|
||||
data class UnfinishedBackupFound(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : BackupDialog()
|
||||
|
||||
data class ConfirmDiscardingBackup(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : BackupDialog()
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Onboarding.*
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class BackupMiddleware {
|
||||
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is BackupAction) handleBackupAction(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
|
||||
when (action) {
|
||||
is BackupAction.DiscardBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.DiscardSavedBackup -> {
|
||||
mainScope.launch {
|
||||
backupService.discardSavedBackup()
|
||||
|
||||
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
|
||||
val cardRepository = store.inject(DaggerGraphState::cardRepository)
|
||||
val unfinishedBackup = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
|
||||
|
||||
cardRepository.finishCardActivation(unfinishedBackup.card.cardId)
|
||||
onboardingRepository.clearUnfinishedFinalizeOnboarding()
|
||||
Analytics.send(Finished())
|
||||
}
|
||||
}
|
||||
is BackupAction.ResumeFoundUnfinishedBackup -> {
|
||||
if (action.unfinishedBackupScanResponse != null) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = action.unfinishedBackupScanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.ContinueFinalize,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +1,19 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class OnboardingWalletAction : Action {
|
||||
data object Init : OnboardingWalletAction()
|
||||
data object GetToCreateWalletStep : OnboardingWalletAction()
|
||||
data object CreateWallet : OnboardingWalletAction()
|
||||
data class WalletWasCreated(
|
||||
val shouldSendAnalyticsEvent: Boolean,
|
||||
val result: CompletionResult<CreateProductWalletTaskResponse>,
|
||||
) : OnboardingWalletAction()
|
||||
|
||||
data object Done : OnboardingWalletAction()
|
||||
data class FinishOnboarding(val scope: CoroutineScope) : OnboardingWalletAction()
|
||||
|
||||
data object ResumeBackup : OnboardingWalletAction()
|
||||
|
||||
data class LoadArtwork(val cardArtworkUriForUnfinishedBackup: Uri? = null) : OnboardingWalletAction()
|
||||
class SetPrimaryCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||
class SetSecondCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||
class SetThirdCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||
|
||||
data object OnBackPressed : OnboardingWalletAction()
|
||||
|
||||
data class WalletSaved(val userWalletId: UserWalletId) : OnboardingWalletAction()
|
||||
}
|
||||
|
||||
sealed class OnboardingWallet2Action : OnboardingWalletAction() {
|
||||
data class Init(val maxProgress: Int) : OnboardingWallet2Action()
|
||||
data class SetDependencies(val maxProgress: Int) : OnboardingWallet2Action()
|
||||
|
||||
data class CreateWallet(
|
||||
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) : OnboardingWallet2Action()
|
||||
|
||||
data class ImportWallet(
|
||||
val mnemonicComponents: List<String>,
|
||||
val passphrase: String?,
|
||||
val seedPhraseSource: SeedPhraseSource,
|
||||
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) : OnboardingWallet2Action()
|
||||
|
||||
data class WalletWasCreated(
|
||||
val result: CompletionResult<CreateWalletResponse>,
|
||||
) : OnboardingWallet2Action()
|
||||
}
|
||||
|
||||
sealed class BackupAction : Action {
|
||||
|
||||
data object IntroduceBackup : BackupAction()
|
||||
data object StartBackup : BackupAction()
|
||||
data object SkipBackup : BackupAction()
|
||||
|
||||
data object ErrorInBackupCard : BackupAction()
|
||||
data object StartAddingPrimaryCard : BackupAction()
|
||||
data object ScanPrimaryCard : BackupAction()
|
||||
data class SetHasRing(val hasRing: Boolean) : BackupAction()
|
||||
|
||||
/**
|
||||
* Check for unfinished backup of standard Wallets
|
||||
* See more GlobalAction.Onboarding.StartForUnfinishedBackup
|
||||
*/
|
||||
data object CheckForUnfinishedBackup : BackupAction()
|
||||
|
||||
data object StartAddingBackupCards : BackupAction()
|
||||
data object AddBackupCard : BackupAction() {
|
||||
data class Success(val card: Card) : BackupAction()
|
||||
data class ChangeButtonLoading(val isLoading: Boolean) : BackupAction()
|
||||
}
|
||||
|
||||
data object FinishAddingBackupCards : BackupAction()
|
||||
|
||||
data object ShowAccessCodeInfoScreen : BackupAction()
|
||||
data object ShowEnterAccessCodeScreen : BackupAction()
|
||||
data class CheckAccessCode(val accessCode: String) : BackupAction()
|
||||
data class SetAccessCodeError(val error: AccessCodeError?) : BackupAction()
|
||||
data class SaveFirstAccessCode(val accessCode: String) : BackupAction()
|
||||
data class SaveAccessCodeConfirmation(val accessCodeConfirmation: String) : BackupAction()
|
||||
data object OnAccessCodeDialogClosed : BackupAction()
|
||||
|
||||
data object PrepareToWritePrimaryCard : BackupAction()
|
||||
data object WritePrimaryCard : BackupAction()
|
||||
data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction()
|
||||
data class WriteBackupCard(val cardNumber: Int) : BackupAction()
|
||||
|
||||
data class FinishBackup(val withAnalytics: Boolean = true) : BackupAction()
|
||||
|
||||
data class BackupFinished(val userWalletId: UserWalletId?) : BackupAction()
|
||||
|
||||
data object DiscardBackup : BackupAction()
|
||||
data object DiscardSavedBackup : BackupAction()
|
||||
|
||||
data class ResumeFoundUnfinishedBackup(
|
||||
val unfinishedBackupScanResponse: ScanResponse?,
|
||||
) : BackupAction()
|
||||
|
||||
data class ResetBackupCard(val cardId: String) : BackupAction()
|
||||
}
|
||||
|
|
@ -1,849 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.ScreensSources
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS
|
||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
object OnboardingWalletMiddleware {
|
||||
val handler = onboardingWalletMiddleware
|
||||
}
|
||||
|
||||
private const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is OnboardingWallet2Action -> handleWallet2Action(action)
|
||||
else -> handleWalletAction(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleWalletAction(action: Action) {
|
||||
if (action !is OnboardingWalletAction) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
val card = scanResponse?.card
|
||||
|
||||
val onboardingWalletState = store.state.onboardingWalletState
|
||||
|
||||
when (action) {
|
||||
OnboardingWalletAction.Init -> {
|
||||
ifNotNull(onboardingManager, card) { manager, notNullCard ->
|
||||
mainScope.launch {
|
||||
if (!manager.isActivationStarted(notNullCard.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
when {
|
||||
card == null -> {
|
||||
// it's possible when found unfinished backup
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> {
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> {
|
||||
store.dispatch(BackupAction.FinishBackup())
|
||||
}
|
||||
else -> {
|
||||
store.dispatch(OnboardingWalletAction.GetToCreateWalletStep)
|
||||
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.LoadArtwork -> {
|
||||
scope.launch {
|
||||
val cardArtwork = when (onboardingManager) {
|
||||
null -> {
|
||||
// onboardingManager is null when backup started from previously interrupted
|
||||
val primaryCardId = backupService.primaryCardId
|
||||
val cardPublicKey = backupService.primaryPublicKey
|
||||
if (primaryCardId != null && cardPublicKey != null) {
|
||||
// uses when no scanResponse and backup state restored
|
||||
loadArtworkForCard(
|
||||
cardId = primaryCardId,
|
||||
cardPublicKey = cardPublicKey,
|
||||
defaultArtwork = action.cardArtworkUriForUnfinishedBackup,
|
||||
)
|
||||
} else {
|
||||
action.cardArtworkUriForUnfinishedBackup
|
||||
}
|
||||
}
|
||||
else -> onboardingManager.loadArtworkUrl()
|
||||
.takeIf { it != Artwork.DEFAULT_IMG_URL }
|
||||
?.let { Uri.parse(it) }
|
||||
}
|
||||
store.dispatchOnMain(OnboardingWalletAction.SetPrimaryCardArtworkUrl(cardArtwork))
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createProductWallet(
|
||||
scanResponse,
|
||||
globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(true, result))
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.WalletWasCreated -> {
|
||||
scanResponse ?: return
|
||||
when (val result = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
if (action.shouldSendAnalyticsEvent) {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
}
|
||||
// here we must use updated scanResponse after createWallet & derivation
|
||||
val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
mainScope.launch {
|
||||
onboardingManager.startActivation(updatedResponse.card.cardId)
|
||||
store.dispatchWithMain(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.FinishOnboarding -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
navigateToWalletScreen()
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
}
|
||||
is OnboardingWalletAction.ResumeBackup -> {
|
||||
val newAction = when (val backupState = backupService.currentState) {
|
||||
BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard
|
||||
is BackupService.State.FinalizingBackupCard -> BackupAction.PrepareToWriteBackupCard(backupState.index)
|
||||
else -> {
|
||||
if (onboardingWalletState.backupState.backupStep == null ||
|
||||
onboardingWalletState.backupState.backupStep == BackupStep.InitBackup ||
|
||||
onboardingWalletState.backupState.backupStep == BackupStep.Finished
|
||||
) {
|
||||
Analytics.send(Onboarding.Backup.ScreenOpened())
|
||||
|
||||
val isWallet2 = scanResponse?.cardTypesResolver?.isWallet2() ?: false
|
||||
if (isWallet2) BackupAction.StartBackup else BackupAction.IntroduceBackup
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
newAction?.let { store.dispatch(it) }
|
||||
}
|
||||
OnboardingWalletAction.OnBackPressed -> handleOnBackPressed(onboardingWalletState)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFinishBackup(scanResponse: ScanResponse, userWallet: UserWallet? = null) {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
|
||||
|
||||
val userWalletId = userWallet?.walletId ?: UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
if (backupState.hasRing && userWalletId != null) {
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::walletsRepository).setHasWalletsWithRing(userWalletId = userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
OnboardingHelper.saveWallet(
|
||||
alreadyCreatedWallet = userWallet,
|
||||
scanResponse = updatedScanResponse,
|
||||
accessCode = backupState.accessCode,
|
||||
backupCardsIds = backupState.backupCardIds,
|
||||
hasBackupError = backupState.hasBackupError,
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToWalletScreen() {
|
||||
mainScope.launch {
|
||||
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
if (tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen()) {
|
||||
delay(timeMillis = 1_800)
|
||||
store.dispatchNavigationAction { push(AppRoute.SaveWallet) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun readCard(): CompletionResult<ScanResponse> {
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
return store.inject(DaggerGraphState::scanCardProcessor).scan(analyticsSource = ScreensSources.Intro)
|
||||
}
|
||||
|
||||
private suspend fun loadArtworkForCard(cardId: String, cardPublicKey: ByteArray, defaultArtwork: Uri?): Uri {
|
||||
val onlineCardVerifier = store.inject(DaggerGraphState::onlineCardVerifier)
|
||||
|
||||
return when (val cardInfo = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
|
||||
is Result.Success -> {
|
||||
val artworkId = cardInfo.data.artwork?.id
|
||||
if (artworkId.isNullOrEmpty()) {
|
||||
defaultArtwork ?: Uri.EMPTY
|
||||
} else {
|
||||
Uri.parse(CardArtworksProvider.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId))
|
||||
}
|
||||
}
|
||||
is Result.Failure -> defaultArtwork ?: Uri.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleWallet2Action(action: OnboardingWallet2Action) {
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
|
||||
when (action) {
|
||||
is OnboardingWallet2Action.Init -> {
|
||||
if (scanResponse?.cardTypesResolver?.isWallet2() == true) {
|
||||
store.dispatch(OnboardingWallet2Action.SetDependencies(action.maxProgress))
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (
|
||||
val result = tangemSdkManager.createProductWallet(
|
||||
scanResponse,
|
||||
globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.ImportWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (
|
||||
val result = tangemSdkManager.importWallet(
|
||||
scanResponse = scanResponse,
|
||||
passphrase = action.passphrase,
|
||||
mnemonic = action.mnemonicComponents.joinToString(" "),
|
||||
shouldReset = globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val creationType = when (action.seedPhraseSource) {
|
||||
SeedPhraseSource.IMPORTED -> AnalyticsParam.WalletCreationType.SeedImport
|
||||
SeedPhraseSource.GENERATED -> AnalyticsParam.WalletCreationType.NewSeed
|
||||
}
|
||||
Analytics.send(
|
||||
event = Onboarding.CreateWallet.WalletCreatedSuccessfully(
|
||||
creationType = creationType,
|
||||
seedPhraseLength = action.mnemonicComponents.size,
|
||||
),
|
||||
)
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.WalletWasCreated -> {
|
||||
val result = when (val mediateResult = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = CreateProductWalletTaskResponse(
|
||||
card = mediateResult.data.card,
|
||||
derivedKeys = mediateResult.data.derivedKeys,
|
||||
primaryCard = mediateResult.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(mediateResult.error)
|
||||
}
|
||||
}
|
||||
// do not send analytics event for wallet2 flow, cause its already sent
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(false, result))
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleActivationError() {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.WalletActivationError(
|
||||
onConfirm = {
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(true))
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateScanResponseAfterBackup(scanResponse: ScanResponse, backupState: BackupState): ScanResponse {
|
||||
val card = if (backupState.backupCardsNumber > 0) {
|
||||
val cardsCount = backupState.backupCardsNumber
|
||||
scanResponse.card.copy(
|
||||
backupStatus = CardDTO.BackupStatus.Active(cardCount = cardsCount),
|
||||
isAccessCodeSet = true,
|
||||
)
|
||||
} else {
|
||||
scanResponse.card
|
||||
}
|
||||
return scanResponse.copy(card = card)
|
||||
}
|
||||
|
||||
class BackupMiddleware {
|
||||
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is BackupAction) handleBackupAction(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
val globalState = appState()?.globalState ?: return
|
||||
val onboardingWalletState = appState()?.onboardingWalletState ?: return
|
||||
|
||||
val backupState = onboardingWalletState.backupState
|
||||
var scanResponse = globalState.onboardingState.onboardingManager?.scanResponse
|
||||
val card = scanResponse?.card
|
||||
|
||||
when (action) {
|
||||
is BackupAction.StartBackup -> {
|
||||
Analytics.send(Onboarding.Backup.Started())
|
||||
backupService.discardSavedBackup()
|
||||
val primaryCard = scanResponse?.primaryCard
|
||||
if (primaryCard != null) {
|
||||
backupService.setPrimaryCard(primaryCard)
|
||||
store.dispatch(BackupAction.StartAddingBackupCards)
|
||||
} else {
|
||||
store.dispatch(BackupAction.StartAddingPrimaryCard)
|
||||
}
|
||||
}
|
||||
is BackupAction.ScanPrimaryCard -> {
|
||||
val iconScanRes = if (scanResponse?.cardTypesResolver?.isRing() == true) {
|
||||
R.drawable.img_hand_scan_ring
|
||||
} else {
|
||||
null
|
||||
}
|
||||
backupService.readPrimaryCard(iconScanRes = iconScanRes, cardId = card?.cardId) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.StartAddingBackupCards)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.AddBackupCard -> {
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.ChangeButtonLoading(true))
|
||||
backupService.addBackupCard { result ->
|
||||
backupService.skipCompatibilityChecks = false
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).sdk.config.filter.cardIdFilter = null
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.ChangeButtonLoading(false))
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
updateArtworks(backupService.addedBackupCardsCount, result.data)
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.Success(result.data))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
when (val error = result.error) {
|
||||
is TangemSdkError.CardVerificationFailed -> {
|
||||
Analytics.send(
|
||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(
|
||||
ScreensSources.Backup,
|
||||
),
|
||||
)
|
||||
|
||||
val resource = error.localizedDescriptionRes()
|
||||
val resId = resource.resId ?: com.tangem.core.ui.R.string.common_unknown_error
|
||||
val resArgs = resource.args.map { it.value }
|
||||
|
||||
store.inject(DaggerGraphState::uiMessageSender).send(
|
||||
message = Dialogs.cardVerificationFailed(
|
||||
errorDescription = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
onRequestSupport = {
|
||||
mainScope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||
.invoke(type = FeedbackEmailType.CardAttestationFailed)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
BackupDialog.ResetBackupCard(error.cardId),
|
||||
),
|
||||
)
|
||||
}
|
||||
is TangemSdkError.IssuerSignatureLoadingFailed -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(BackupDialog.AttestationFailed),
|
||||
)
|
||||
}
|
||||
else -> Analytics.sendException(ExceptionAnalyticsEvent(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.FinishAddingBackupCards -> {
|
||||
if (backupService.addedBackupCardsCount == backupState.maxBackupCards) {
|
||||
store.dispatchOnMain(BackupAction.ShowAccessCodeInfoScreen)
|
||||
} else {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.AddMoreBackupCards))
|
||||
}
|
||||
}
|
||||
is BackupAction.ShowEnterAccessCodeScreen -> {
|
||||
Analytics.send(Onboarding.Backup.SettingAccessCodeStarted())
|
||||
}
|
||||
is BackupAction.CheckAccessCode -> {
|
||||
Analytics.send(Onboarding.Backup.AccessCodeEntered())
|
||||
if (action.accessCode.length < 4) {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(AccessCodeError.CodeTooShort))
|
||||
} else {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(null))
|
||||
store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode))
|
||||
}
|
||||
}
|
||||
is BackupAction.SaveAccessCodeConfirmation -> {
|
||||
Analytics.send(Onboarding.Backup.AccessCodeReEntered())
|
||||
if (action.accessCodeConfirmation == backupState.accessCode) {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(null))
|
||||
backupService.setAccessCode(action.accessCodeConfirmation)
|
||||
store.dispatch(BackupAction.PrepareToWritePrimaryCard)
|
||||
} else {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(AccessCodeError.CodesDoNotMatch))
|
||||
}
|
||||
}
|
||||
is BackupAction.WritePrimaryCard -> {
|
||||
val isRing = scanResponse?.cardTypesResolver?.isRing() == true
|
||||
val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null
|
||||
|
||||
store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing))
|
||||
|
||||
tangemSdkManager.changeProductType(isRing)
|
||||
backupService.proceedBackup(iconScanRes = iconScanRes) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(1))
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
tangemSdkManager.clearProductType()
|
||||
}
|
||||
}
|
||||
is BackupAction.WriteBackupCard -> {
|
||||
val cardIndex = if (action.cardNumber > 0) action.cardNumber - 1 else action.cardNumber
|
||||
val backupCard = backupState.backupCards.getOrNull(cardIndex)
|
||||
val isRing = backupCard.isRing()
|
||||
val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null
|
||||
|
||||
store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing))
|
||||
|
||||
tangemSdkManager.changeProductType(isRing)
|
||||
backupService.proceedBackup(iconScanRes = iconScanRes) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val backupValidator = BackupValidator()
|
||||
if (!backupValidator.isValidBackupStatus(CardDTO(result.data))) {
|
||||
store.dispatchOnMain(BackupAction.ErrorInBackupCard)
|
||||
}
|
||||
if (backupService.currentState == BackupService.State.Finished) {
|
||||
store.dispatchOnMain(BackupAction.FinishBackup())
|
||||
} else {
|
||||
store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
when (val error = result.error) {
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
BackupDialog.ResetBackupCard(error.cardId),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tangemSdkManager.clearProductType()
|
||||
}
|
||||
}
|
||||
is BackupAction.DiscardBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.DiscardSavedBackup -> {
|
||||
mainScope.launch {
|
||||
backupService.primaryCardId?.let { // always null for onboarding v2
|
||||
Analytics.send(Onboarding.Finished())
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(it)
|
||||
}
|
||||
backupService.discardSavedBackup()
|
||||
|
||||
val isOnboardingV2Enabled = store.inject(DaggerGraphState::onboardingV2FeatureToggles)
|
||||
.isOnboardingV2Enabled
|
||||
|
||||
if (isOnboardingV2Enabled) {
|
||||
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
|
||||
val cardRepository = store.inject(DaggerGraphState::cardRepository)
|
||||
val unfinishedBackup = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
|
||||
|
||||
cardRepository.finishCardActivation(unfinishedBackup.card.cardId)
|
||||
onboardingRepository.clearUnfinishedFinalizeOnboarding()
|
||||
Analytics.send(Onboarding.Finished())
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.CheckForUnfinishedBackup -> {
|
||||
val isOnboardingV2Enabled = store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled
|
||||
|
||||
if (isOnboardingV2Enabled) {
|
||||
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
|
||||
|
||||
mainScope.launch {
|
||||
val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
|
||||
}
|
||||
} else if (backupService.hasIncompletedBackup) {
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound()))
|
||||
}
|
||||
}
|
||||
is BackupAction.ResumeFoundUnfinishedBackup -> {
|
||||
if (action.unfinishedBackupScanResponse != null) {
|
||||
// onboarding V2
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = action.unfinishedBackupScanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.ContinueFinalize,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
store.dispatch(
|
||||
GlobalAction.Onboarding.StartForUnfinishedBackup(
|
||||
addedBackupCardsCount = backupService.addedBackupCardsCount,
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
|
||||
}
|
||||
}
|
||||
is BackupAction.SkipBackup -> {
|
||||
Analytics.send(Onboarding.Backup.Skipped())
|
||||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
scope.launch {
|
||||
launch {
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(
|
||||
cardIds = gatherCardIds(backupState, card),
|
||||
)
|
||||
}
|
||||
|
||||
with(scanResponse) {
|
||||
if (this == null) {
|
||||
when (val result = readCard()) {
|
||||
is CompletionResult.Success -> handleFinishBackup(result.data)
|
||||
is CompletionResult.Failure -> store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
} else {
|
||||
handleFinishBackup(scanResponse = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.FinishBackup -> {
|
||||
scope.launch {
|
||||
if (action.withAnalytics) {
|
||||
Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber))
|
||||
}
|
||||
|
||||
var userWallet: UserWallet? = null
|
||||
if (scanResponse != null) {
|
||||
scanResponse = updateScanResponseAfterBackup(scanResponse!!, backupState)
|
||||
userWallet = createUserWallet(
|
||||
scanResponse = requireNotNull(value = scanResponse, lazyMessage = { "ScanResponse is null" }),
|
||||
backupState = backupState,
|
||||
)
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
|
||||
when (val result = readCard()) {
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
return@launch
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
scanResponse = result.data
|
||||
userWallet = createUserWallet(scanResponse = result.data, backupState = backupState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
when (backupState.startedSource) {
|
||||
BackupStartedSource.Onboarding -> saveWallet(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWallet = userWallet,
|
||||
scanResponse = scanResponse,
|
||||
backupState = backupState,
|
||||
)
|
||||
BackupStartedSource.CreateBackup -> updateWallet(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWallet = userWallet,
|
||||
backupState = backupState,
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
if (userWallet.scanResponse.cardTypesResolver.isWallet2() && userWallet.isImported) {
|
||||
store.inject(DaggerGraphState::walletsRepository).markWallet2WasCreated(userWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
val notActivatedCardIds = gatherCardIds(backupState, card).mapNotNull {
|
||||
if (store.state.globalState.onboardingState.onboardingManager?.isActivationFinished(it) == true) {
|
||||
null
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
// All cardIds may already be activated if the backup was skipped before.
|
||||
if (notActivatedCardIds.isEmpty()) {
|
||||
delay(1000)
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWallet.walletId))
|
||||
return@launch
|
||||
}
|
||||
|
||||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds)
|
||||
handleFinishBackup(requireNotNull(scanResponse), userWallet)
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet.walletId))
|
||||
}
|
||||
}
|
||||
|
||||
is BackupAction.ResetBackupCard -> {
|
||||
scope.launch { tangemSdkManager.resetToFactorySettings(action.cardId, false) }
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun Card?.isRing(): Boolean {
|
||||
return this?.let { RING_BATCH_IDS.contains(batchId) || batchId.startsWith(RING_BATCH_PREFIX) } ?: false
|
||||
}
|
||||
|
||||
private suspend fun saveWallet(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWallet: UserWallet,
|
||||
scanResponse: ScanResponse?,
|
||||
backupState: BackupState,
|
||||
) {
|
||||
userWalletsListManager.save(
|
||||
userWallet = userWallet.copy(
|
||||
scanResponse = updateScanResponseAfterBackup(
|
||||
scanResponse = requireNotNull(
|
||||
value = scanResponse,
|
||||
lazyMessage = { "ScanResponse is null" },
|
||||
),
|
||||
backupState = backupState,
|
||||
),
|
||||
),
|
||||
canOverride = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateWallet(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWallet: UserWallet,
|
||||
backupState: BackupState,
|
||||
) {
|
||||
userWalletsListManager.update(
|
||||
userWalletId = userWallet.walletId,
|
||||
update = { wallet ->
|
||||
wallet.copy(
|
||||
scanResponse = updateScanResponseAfterBackup(
|
||||
scanResponse = wallet.scanResponse,
|
||||
backupState = backupState,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: BackupState): UserWallet {
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
return requireNotNull(
|
||||
value = userWalletBuilder
|
||||
.backupCardsIds(backupState.backupCardIds.toSet())
|
||||
.hasBackupError(backupState.hasBackupError)
|
||||
.build(),
|
||||
lazyMessage = { "User wallet not created" },
|
||||
)
|
||||
}
|
||||
|
||||
fun updateArtworks(addedBackupCardsCount: Int, card: Card) {
|
||||
mainScope.launch {
|
||||
withIOContext {
|
||||
val imageUri = loadArtworkForCard(card.cardId, card.cardPublicKey, Uri.EMPTY)
|
||||
when (addedBackupCardsCount) {
|
||||
1 -> {
|
||||
store.dispatchOnMain(OnboardingWalletAction.SetSecondCardArtworkUrl(imageUri))
|
||||
}
|
||||
2 -> {
|
||||
store.dispatchOnMain(OnboardingWalletAction.SetThirdCardArtworkUrl(imageUri))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun gatherCardIds(backupState: BackupState, card: CardDTO?): List<String> {
|
||||
return (listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
|
||||
.filterNotNull()
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun handleOnBackPressed(state: OnboardingWalletState) {
|
||||
when (state.backupState.backupStep) {
|
||||
null, BackupStep.InitBackup, BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode,
|
||||
BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard,
|
||||
-> {
|
||||
showInterruptOnboardingDialog()
|
||||
}
|
||||
is BackupStep.WriteBackupCard -> {
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress))
|
||||
}
|
||||
BackupStep.Finished -> {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showInterruptOnboardingDialog() {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.InterruptOnboarding(
|
||||
onOk = {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.domain.redux.OnboardingManageTokensAction
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import org.rekotlin.Action
|
||||
|
||||
object OnboardingWalletReducer {
|
||||
fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState {
|
||||
val state = appState.onboardingWalletState
|
||||
val backupState = state.backupState
|
||||
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding -> ReducerForGlobalAction.reduce(action, state)
|
||||
is BackupAction.BackupFinished -> {
|
||||
state.copy(
|
||||
step = if (action.userWalletId != null && backupState.startedSource == BackupStartedSource.Onboarding) {
|
||||
OnboardingWalletStep.ManageTokens
|
||||
} else {
|
||||
OnboardingWalletStep.Done
|
||||
},
|
||||
userWalletId = action.userWalletId,
|
||||
)
|
||||
}
|
||||
is BackupAction -> state.copy(backupState = BackupReducer.reduce(action = action, state = state.backupState))
|
||||
is OnboardingWallet2Action -> OnboardingWallet2Reducer.reduce(action, state)
|
||||
is OnboardingWalletAction.GetToCreateWalletStep -> state.copy(
|
||||
step = OnboardingWalletStep.CreateWallet,
|
||||
)
|
||||
is OnboardingWalletAction.ResumeBackup -> state.copy(step = OnboardingWalletStep.Backup)
|
||||
is OnboardingWalletAction.SetPrimaryCardArtworkUrl -> {
|
||||
state.copy(
|
||||
walletImages = state.walletImages.copy(primaryCardImage = action.artworkUri),
|
||||
)
|
||||
}
|
||||
is OnboardingWalletAction.SetSecondCardArtworkUrl -> {
|
||||
state.copy(
|
||||
walletImages = state.walletImages.copy(secondCardImage = action.artworkUri),
|
||||
)
|
||||
}
|
||||
is OnboardingWalletAction.SetThirdCardArtworkUrl -> {
|
||||
state.copy(
|
||||
walletImages = state.walletImages.copy(thirdCardImage = action.artworkUri),
|
||||
)
|
||||
}
|
||||
is OnboardingManageTokensAction.CurrenciesSaved -> state.copy(step = OnboardingWalletStep.Done)
|
||||
is OnboardingWalletAction.WalletSaved -> state.copy(
|
||||
step = when (backupState.startedSource) {
|
||||
BackupStartedSource.Onboarding -> OnboardingWalletStep.ManageTokens
|
||||
BackupStartedSource.CreateBackup -> OnboardingWalletStep.Done
|
||||
},
|
||||
userWalletId = action.userWalletId,
|
||||
)
|
||||
is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private object ReducerForGlobalAction {
|
||||
|
||||
private const val MAX_BACKUP_CARDS = 2
|
||||
|
||||
fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState {
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
OnboardingWalletState(
|
||||
backupState = state.backupState.copy(
|
||||
maxBackupCards = MAX_BACKUP_CARDS,
|
||||
canSkipBackup = action.canSkipBackup,
|
||||
startedSource = action.source,
|
||||
),
|
||||
)
|
||||
}
|
||||
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
|
||||
OnboardingWalletState(
|
||||
backupState = state.backupState.copy(
|
||||
maxBackupCards = action.addedBackupCardsCount,
|
||||
canSkipBackup = false,
|
||||
isInterruptedBackup = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object OnboardingWallet2Reducer {
|
||||
fun reduce(action: OnboardingWallet2Action, state: OnboardingWalletState): OnboardingWalletState = when (action) {
|
||||
is OnboardingWallet2Action.SetDependencies -> state.copy(
|
||||
wallet2State = OnboardingWallet2State(
|
||||
maxProgress = action.maxProgress,
|
||||
),
|
||||
)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private object BackupReducer {
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun reduce(action: BackupAction, state: BackupState): BackupState {
|
||||
return when (action) {
|
||||
is BackupAction.IntroduceBackup -> BackupState(
|
||||
backupStep = BackupStep.InitBackup,
|
||||
maxBackupCards = 2,
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
startedSource = state.startedSource,
|
||||
)
|
||||
BackupAction.StartAddingPrimaryCard -> {
|
||||
state.copy(
|
||||
backupStep = BackupStep.ScanOriginCard,
|
||||
maxBackupCards = 2,
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
)
|
||||
}
|
||||
BackupAction.StartAddingBackupCards -> {
|
||||
state.copy(
|
||||
backupStep = BackupStep.AddBackupCards,
|
||||
maxBackupCards = 2,
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
)
|
||||
}
|
||||
is BackupAction.AddBackupCard.Success -> {
|
||||
state.copy(
|
||||
backupCards = state.backupCards + action.card,
|
||||
backupCardsNumber = state.backupCardsNumber + 1,
|
||||
)
|
||||
}
|
||||
is BackupAction.AddBackupCard.ChangeButtonLoading -> state.copy(showBtnLoading = action.isLoading)
|
||||
BackupAction.ShowAccessCodeInfoScreen -> state.copy(backupStep = BackupStep.SetAccessCode)
|
||||
BackupAction.ShowEnterAccessCodeScreen -> state.copy(
|
||||
backupStep = BackupStep.EnterAccessCode,
|
||||
accessCodeError = null,
|
||||
)
|
||||
is BackupAction.SaveFirstAccessCode -> state.copy(
|
||||
backupStep = BackupStep.ReenterAccessCode,
|
||||
accessCode = action.accessCode,
|
||||
)
|
||||
is BackupAction.SetAccessCodeError -> state.copy(accessCodeError = action.error)
|
||||
is BackupAction.PrepareToWritePrimaryCard -> if (state.primaryCardId == null) {
|
||||
state.copy(
|
||||
primaryCardId = backupService.primaryCardId,
|
||||
primaryCardBatchId = backupService.primaryCardBatchId,
|
||||
backupCardIds = backupService.backupCardIds,
|
||||
backupCardBatchIds = backupService.backupCardsBatchIds,
|
||||
backupCardsNumber = backupService.backupCardIds.size,
|
||||
backupStep = BackupStep.WritePrimaryCard,
|
||||
)
|
||||
} else {
|
||||
state.copy(backupStep = BackupStep.WritePrimaryCard)
|
||||
}
|
||||
is BackupAction.PrepareToWriteBackupCard -> if (state.primaryCardId == null) {
|
||||
state.copy(
|
||||
primaryCardId = backupService.primaryCardId,
|
||||
primaryCardBatchId = backupService.primaryCardBatchId,
|
||||
backupCardIds = backupService.backupCardIds,
|
||||
backupCardBatchIds = backupService.backupCardsBatchIds,
|
||||
backupCardsNumber = backupService.backupCardIds.size,
|
||||
backupStep = BackupStep.WriteBackupCard(action.cardNumber),
|
||||
)
|
||||
} else {
|
||||
state.copy(backupStep = BackupStep.WriteBackupCard(action.cardNumber))
|
||||
}
|
||||
is BackupAction.ErrorInBackupCard -> state.copy(hasBackupError = true)
|
||||
is BackupAction.SkipBackup -> state.copy(backupStep = BackupStep.Finished)
|
||||
is BackupAction.FinishBackup -> state.copy(backupStep = BackupStep.Finished)
|
||||
BackupAction.OnAccessCodeDialogClosed -> state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
BackupAction.DiscardBackup -> BackupState()
|
||||
is BackupAction.SetHasRing -> {
|
||||
// Don't update if state.has ring is already true
|
||||
if (state.hasRing) {
|
||||
state
|
||||
} else {
|
||||
state.copy(hasRing = action.hasRing)
|
||||
}
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingWalletState(
|
||||
val step: OnboardingWalletStep = OnboardingWalletStep.None,
|
||||
val wallet2State: OnboardingWallet2State? = null,
|
||||
val backupState: BackupState = BackupState(),
|
||||
val walletImages: WalletImages = WalletImages(),
|
||||
val showConfetti: Boolean = false,
|
||||
val isRingOnboarding: Boolean = false,
|
||||
val userWalletId: UserWalletId? = null,
|
||||
) : StateType {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun getMaxProgress(): Int {
|
||||
val baseProgress = 7
|
||||
return getWallet2Progress() + baseProgress
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "MagicNumber")
|
||||
fun getProgressStep(): Int {
|
||||
val progressByStep = when (step) {
|
||||
OnboardingWalletStep.CreateWallet, OnboardingWalletStep.None -> 1
|
||||
OnboardingWalletStep.Backup -> {
|
||||
when (backupState.backupStep) {
|
||||
null -> 2
|
||||
BackupStep.InitBackup -> 2
|
||||
BackupStep.ScanOriginCard -> 3
|
||||
BackupStep.AddBackupCards -> 4
|
||||
BackupStep.EnterAccessCode -> 4
|
||||
BackupStep.ReenterAccessCode -> 4
|
||||
BackupStep.SetAccessCode -> 4
|
||||
BackupStep.WritePrimaryCard, is BackupStep.WriteBackupCard -> 5
|
||||
BackupStep.Finished -> 6
|
||||
}
|
||||
}
|
||||
OnboardingWalletStep.ManageTokens -> 6
|
||||
OnboardingWalletStep.Done -> getMaxProgress()
|
||||
}
|
||||
|
||||
return getWallet2Progress() + progressByStep
|
||||
}
|
||||
|
||||
private fun getWallet2Progress(): Int = wallet2State?.maxProgress ?: 0
|
||||
}
|
||||
|
||||
data class WalletImages(
|
||||
val primaryCardImage: Uri? = null,
|
||||
val secondCardImage: Uri? = null,
|
||||
val thirdCardImage: Uri? = null,
|
||||
)
|
||||
|
||||
data class OnboardingWallet2State(
|
||||
val maxProgress: Int,
|
||||
)
|
||||
|
||||
enum class OnboardingWalletStep {
|
||||
None, CreateWallet, Backup, ManageTokens, Done
|
||||
}
|
||||
|
||||
data class BackupState(
|
||||
val primaryCardId: String? = null,
|
||||
val primaryCardBatchId: String? = null,
|
||||
val backupCardsNumber: Int = 0,
|
||||
val backupCards: List<Card> = emptyList(),
|
||||
val backupCardIds: List<CardId> = emptyList(),
|
||||
val backupCardBatchIds: List<CardId> = emptyList(),
|
||||
@Transient
|
||||
val backupCardsArtworks: Map<CardId, Bitmap> = emptyMap(),
|
||||
val accessCode: String = "",
|
||||
val accessCodeError: AccessCodeError? = null,
|
||||
val backupStep: BackupStep? = null,
|
||||
val maxBackupCards: Int = 2,
|
||||
val canSkipBackup: Boolean = true,
|
||||
val isInterruptedBackup: Boolean = false,
|
||||
val showBtnLoading: Boolean = false,
|
||||
val hasBackupError: Boolean = false,
|
||||
val startedSource: BackupStartedSource = BackupStartedSource.Onboarding,
|
||||
val hasRing: Boolean = false,
|
||||
)
|
||||
|
||||
enum class BackupStartedSource {
|
||||
Onboarding, CreateBackup
|
||||
}
|
||||
|
||||
enum class AccessCodeError {
|
||||
CodeTooShort, CodesDoNotMatch
|
||||
}
|
||||
|
||||
typealias CardId = String
|
||||
|
||||
sealed class BackupStep {
|
||||
data object InitBackup : BackupStep()
|
||||
data object ScanOriginCard : BackupStep()
|
||||
data object AddBackupCards : BackupStep()
|
||||
data object SetAccessCode : BackupStep()
|
||||
data object EnterAccessCode : BackupStep()
|
||||
data object ReenterAccessCode : BackupStep()
|
||||
data object WritePrimaryCard : BackupStep()
|
||||
data class WriteBackupCard(val cardNumber: Int) : BackupStep()
|
||||
data object Finished : BackupStep()
|
||||
}
|
||||
|
||||
sealed class BackupDialog : StateDialog {
|
||||
data object AttestationFailed : BackupDialog()
|
||||
data object AddMoreBackupCards : BackupDialog()
|
||||
data object BackupInProgress : BackupDialog()
|
||||
|
||||
data class UnfinishedBackupFound(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : BackupDialog()
|
||||
|
||||
data class ConfirmDiscardingBackup(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : BackupDialog()
|
||||
|
||||
data class ResetBackupCard(val cardId: String) : BackupDialog()
|
||||
}
|
||||
|
|
@ -1,314 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.postUiDelayBg
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.BackupCardType.*
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface BackupAnimator {
|
||||
fun updateBackupState(backupState: BackupState)
|
||||
fun setupCreateWalletState()
|
||||
fun showBackupIntro(state: BackupState)
|
||||
fun showScanOriginCard()
|
||||
fun showAddBackupCards(state: BackupState, backupCardsCounts: Int)
|
||||
fun showWritePrimaryCard(state: BackupState)
|
||||
fun showWriteBackupCard(state: BackupState, backupCard: Int)
|
||||
fun showSuccess(onEnd: () -> Unit)
|
||||
}
|
||||
|
||||
class WalletBackupAnimator(
|
||||
private val cardsWidget: WalletCardsWidget,
|
||||
) : BackupAnimator {
|
||||
|
||||
private val viewState: State = when (cardsWidget.leapfrogWidget.getViewsCount()) {
|
||||
2 -> State.TwoCards
|
||||
3 -> State.ThreeCards
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private var currentStep = -1
|
||||
private var firstBackupCardAnimated = false
|
||||
private var secondBackupCardAnimated = false
|
||||
|
||||
override fun updateBackupState(backupState: BackupState) {
|
||||
}
|
||||
|
||||
override fun showBackupIntro(state: BackupState) {
|
||||
currentStep = STEP_BACKUP_INTRO
|
||||
|
||||
ORIGIN.show()
|
||||
FIRST_BACKUP.show()
|
||||
SECOND_BACKUP.show()
|
||||
|
||||
cardsWidget.toWelcome()
|
||||
}
|
||||
|
||||
override fun setupCreateWalletState() {
|
||||
currentStep = STEP_CREATE_WALLET
|
||||
|
||||
cardsWidget.toFolded(false)
|
||||
|
||||
ORIGIN.alpha(1f)
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
SECOND_BACKUP.alpha(1f)
|
||||
}
|
||||
|
||||
override fun showScanOriginCard() {
|
||||
currentStep = STEP_SCAN_ORIGIN_CARD
|
||||
|
||||
cardsWidget.toFolded()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) {
|
||||
when (backupCardsCounts) {
|
||||
0 -> {
|
||||
when (currentStep) {
|
||||
UNDEFINED, STEP_CREATE_WALLET -> {
|
||||
cardsWidget.toFolded(false) { cardsWidget.toFan() }
|
||||
|
||||
FIRST_BACKUP.animateAlpha(0.6f, 0)
|
||||
SECOND_BACKUP.animateAlpha(0.2f, 400)
|
||||
}
|
||||
STEP_BACKUP_INTRO -> {
|
||||
cardsWidget.toFolded(duration = 300) { cardsWidget.toFan() }
|
||||
FIRST_BACKUP.animateAlpha(0.6f, 400, 250)
|
||||
SECOND_BACKUP.animateAlpha(0.2f, 400, 250)
|
||||
}
|
||||
STEP_SCAN_ORIGIN_CARD -> {
|
||||
cardsWidget.toFan()
|
||||
FIRST_BACKUP.animateAlpha(0.6f, 400, 250)
|
||||
SECOND_BACKUP.animateAlpha(0.2f, 400, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
cardsWidget.toFan(false)
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
SECOND_BACKUP.alpha(0.2f)
|
||||
}
|
||||
2 -> {
|
||||
cardsWidget.toFan(false)
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
SECOND_BACKUP.alpha(1f)
|
||||
}
|
||||
}
|
||||
|
||||
currentStep = STEP_ADD_BACKUP_CARDS
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun showWritePrimaryCard(state: BackupState) {
|
||||
currentStep = STEP_WRITE_PRIMARY_CARD
|
||||
|
||||
cardsWidget.toLeapfrog(
|
||||
onEndFold = {
|
||||
FIRST_BACKUP.alpha(0.4f)
|
||||
SECOND_BACKUP.alpha(0.2f)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun showWriteBackupCard(state: BackupState, backupCard: Int) {
|
||||
when (viewState) {
|
||||
State.TwoCards -> {
|
||||
if (firstBackupCardAnimated) return
|
||||
firstBackupCardAnimated = true
|
||||
|
||||
val delay = if (currentStep == UNDEFINED) 700L else 0
|
||||
postUiDelayBg(delay) {
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
cardsWidget.leapfrogWidget.leap {
|
||||
ORIGIN.alpha(0.4f)
|
||||
}
|
||||
}
|
||||
}
|
||||
State.ThreeCards -> {
|
||||
when (backupCard) {
|
||||
1 -> {
|
||||
if (firstBackupCardAnimated) return
|
||||
firstBackupCardAnimated = true
|
||||
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
cardsWidget.leapfrogWidget.leap {
|
||||
SECOND_BACKUP.alpha(0.4f)
|
||||
ORIGIN.alpha(0.2f)
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
if (secondBackupCardAnimated) return
|
||||
secondBackupCardAnimated = true
|
||||
|
||||
/*
|
||||
* !!! Workaround !!!
|
||||
* For interrupted backup
|
||||
* */
|
||||
if (!firstBackupCardAnimated) {
|
||||
showWriteBackupCard(state, backupCard = 1)
|
||||
return
|
||||
}
|
||||
|
||||
SECOND_BACKUP.alpha(1f)
|
||||
cardsWidget.leapfrogWidget.leap {
|
||||
ORIGIN.alpha(0.4f)
|
||||
FIRST_BACKUP.alpha(0.2f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
currentStep = STEP_WRITE_BACKUP_CARD
|
||||
}
|
||||
|
||||
override fun showSuccess(onEnd: () -> Unit) {
|
||||
currentStep = STEP_SUCCESS
|
||||
cardsWidget.leapfrogWidget.fold(onEnd = onEnd)
|
||||
}
|
||||
|
||||
private enum class State {
|
||||
ThreeCards,
|
||||
TwoCards,
|
||||
}
|
||||
|
||||
private fun BackupCardType.animateAlpha(alpha: Float, duration: Long = 0L, startDelay: Long = 0L) {
|
||||
when (this) {
|
||||
ORIGIN -> {
|
||||
cardsWidget.getOriginCardView().animate().apply {
|
||||
alpha(alpha)
|
||||
this.startDelay = startDelay
|
||||
this.duration = duration
|
||||
}
|
||||
}
|
||||
FIRST_BACKUP -> {
|
||||
cardsWidget.getFirstBackupCardView().animate().apply {
|
||||
alpha(alpha)
|
||||
this.startDelay = startDelay
|
||||
this.duration = duration
|
||||
}
|
||||
}
|
||||
SECOND_BACKUP -> {
|
||||
if (viewState != State.ThreeCards) return
|
||||
cardsWidget.getSecondBackupCardView().animate().apply {
|
||||
alpha(alpha)
|
||||
this.startDelay = startDelay
|
||||
this.duration = duration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BackupCardType.alpha(alpha: Float) {
|
||||
when (this) {
|
||||
ORIGIN -> cardsWidget.getOriginCardView().alpha = alpha
|
||||
FIRST_BACKUP -> cardsWidget.getFirstBackupCardView().alpha = alpha
|
||||
SECOND_BACKUP -> {
|
||||
if (viewState != State.ThreeCards) return
|
||||
cardsWidget.getSecondBackupCardView().alpha = alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BackupCardType.show() {
|
||||
when (this) {
|
||||
ORIGIN -> cardsWidget.getOriginCardView().show()
|
||||
FIRST_BACKUP -> cardsWidget.getFirstBackupCardView().show()
|
||||
SECOND_BACKUP -> {
|
||||
if (viewState != State.ThreeCards) return
|
||||
cardsWidget.getSecondBackupCardView().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val UNDEFINED = -1
|
||||
private const val STEP_CREATE_WALLET = 0
|
||||
private const val STEP_BACKUP_INTRO = 1
|
||||
private const val STEP_SCAN_ORIGIN_CARD = 2
|
||||
private const val STEP_ADD_BACKUP_CARDS = 3
|
||||
private const val STEP_WRITE_PRIMARY_CARD = 4
|
||||
private const val STEP_WRITE_BACKUP_CARD = 5
|
||||
private const val STEP_SUCCESS = 6
|
||||
}
|
||||
}
|
||||
|
||||
class TestBackupAnimation(
|
||||
private val animator: BackupAnimator,
|
||||
binding: FragmentOnboardingWalletBinding,
|
||||
) : BackupAnimator {
|
||||
|
||||
private lateinit var state: BackupState
|
||||
private var steps = 0
|
||||
|
||||
init {
|
||||
init(binding, this)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) {
|
||||
steps = step
|
||||
when (steps) {
|
||||
0 -> setupCreateWalletState()
|
||||
1 -> showBackupIntro(state)
|
||||
2 -> showScanOriginCard()
|
||||
3 -> showAddBackupCards(state, 0)
|
||||
4 -> showAddBackupCards(state, 1)
|
||||
5 -> showAddBackupCards(state, 2)
|
||||
6 -> showWritePrimaryCard(state)
|
||||
7 -> showWriteBackupCard(state, 1)
|
||||
8 -> showWriteBackupCard(state, 2)
|
||||
else -> setStep(0)
|
||||
}
|
||||
onStepUpdate(steps)
|
||||
}
|
||||
|
||||
fun onNext(onStepUpdate: (Int) -> Unit) {
|
||||
steps++
|
||||
setStep(steps, onStepUpdate)
|
||||
}
|
||||
|
||||
override fun updateBackupState(backupState: BackupState) {
|
||||
this.state = backupState
|
||||
}
|
||||
|
||||
override fun setupCreateWalletState() {
|
||||
animator.setupCreateWalletState()
|
||||
}
|
||||
|
||||
override fun showBackupIntro(state: BackupState) {
|
||||
animator.showBackupIntro(state)
|
||||
}
|
||||
|
||||
override fun showScanOriginCard() {
|
||||
animator.showScanOriginCard()
|
||||
}
|
||||
|
||||
override fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) {
|
||||
animator.showAddBackupCards(state, backupCardsCounts)
|
||||
}
|
||||
|
||||
override fun showWritePrimaryCard(state: BackupState) {
|
||||
animator.showWritePrimaryCard(state)
|
||||
}
|
||||
|
||||
override fun showWriteBackupCard(state: BackupState, backupCard: Int) {
|
||||
animator.showWriteBackupCard(state, backupCard)
|
||||
}
|
||||
|
||||
override fun showSuccess(onEnd: () -> Unit) {
|
||||
animator.showSuccess(onEnd)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun init(binding: FragmentOnboardingWalletBinding, animator: TestBackupAnimation) {
|
||||
val button = MaterialButton(binding.onboardingRoot.context).apply { text = "Next" }
|
||||
button.setOnClickListener { animator.onNext { button.text = "$it" } }
|
||||
binding.onboardingRoot.addView(button, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemBackupInfoAdapterBinding
|
||||
|
||||
class BackupInfoAdapter : RecyclerView.Adapter<BackupInfoViewHolder>() {
|
||||
|
||||
private val data = backupInfoSnippets
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupInfoViewHolder = BackupInfoViewHolder(
|
||||
ItemBackupInfoAdapterBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false,
|
||||
),
|
||||
)
|
||||
|
||||
override fun getItemCount(): Int = data.size
|
||||
|
||||
override fun onBindViewHolder(holder: BackupInfoViewHolder, position: Int) = holder.binding.run {
|
||||
tvHeader.text = root.getString(data[position].header)
|
||||
tvBody.text = root.getString(data[position].body)
|
||||
}
|
||||
}
|
||||
|
||||
private class BackupInfo(
|
||||
@StringRes val header: Int,
|
||||
@StringRes val body: Int,
|
||||
)
|
||||
|
||||
private val backupInfoSnippets = listOf(
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_first,
|
||||
R.string.onboarding_wallet_info_subtitle_first,
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_second,
|
||||
R.string.onboarding_wallet_info_subtitle_second,
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_third,
|
||||
R.string.onboarding_wallet_info_subtitle_third,
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_fourth,
|
||||
R.string.onboarding_wallet_info_subtitle_fourth,
|
||||
),
|
||||
|
||||
)
|
||||
|
||||
class BackupInfoViewHolder(val binding: ItemBackupInfoAdapterBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.windowsize.rememberWindowSize
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseApi
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class OnboardingSeedPhraseStateHandler(
|
||||
private val onboardingSeedPhraseApi: OnboardingSeedPhraseApi = OnboardingSeedPhraseScreen(),
|
||||
private val activity: Activity,
|
||||
) {
|
||||
|
||||
fun newState(
|
||||
walletFragment: OnboardingWalletFragment,
|
||||
state: OnboardingWalletState,
|
||||
seedPhraseViewModel: SeedPhraseViewModel,
|
||||
) {
|
||||
if (state.step == OnboardingWalletStep.CreateWallet && !seedPhraseViewModel.isFinished) {
|
||||
switchToWallet2SeedPhraseOnboarding(walletFragment, seedPhraseViewModel, state.getMaxProgress())
|
||||
} else {
|
||||
switchToWalletOnboarding(walletFragment, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToWalletOnboarding(walletFragment: OnboardingWalletFragment, state: OnboardingWalletState) {
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.hide()
|
||||
walletFragment.pbBinding.pbState.show()
|
||||
walletFragment.binding.onboardingWalletContainer.show()
|
||||
walletFragment.handleOnboardingStep(state)
|
||||
}
|
||||
|
||||
private fun switchToWallet2SeedPhraseOnboarding(
|
||||
walletFragment: OnboardingWalletFragment,
|
||||
viewModel: SeedPhraseViewModel,
|
||||
onboardingWalletMaxProgress: Int,
|
||||
) {
|
||||
walletFragment.pbBinding.pbState.hide()
|
||||
walletFragment.binding.onboardingWalletContainer.hide()
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.show()
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.setContent {
|
||||
val subScreen = viewModel.currentScreen.collectAsState().value
|
||||
setMainScreenToolbarTitle(walletFragment, subScreen)
|
||||
|
||||
TangemTheme(
|
||||
isDark = isSystemInDarkTheme(),
|
||||
windowSize = rememberWindowSize(activity = activity),
|
||||
) {
|
||||
onboardingSeedPhraseApi.ScreenContent(
|
||||
uiState = viewModel.uiState,
|
||||
subScreen = subScreen,
|
||||
progress = viewModel.progress.collectAsState(0).value.toFloat() / onboardingWalletMaxProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMainScreenToolbarTitle(walletFragment: OnboardingWalletFragment, subScreen: SeedPhraseScreen) {
|
||||
val titleResId = when (subScreen) {
|
||||
SeedPhraseScreen.Intro -> R.string.wallet_title
|
||||
SeedPhraseScreen.AboutSeedPhrase,
|
||||
SeedPhraseScreen.YourSeedPhrase,
|
||||
SeedPhraseScreen.CheckSeedPhrase,
|
||||
-> R.string.onboarding_create_wallet_header
|
||||
SeedPhraseScreen.ImportSeedPhrase -> R.string.onboarding_seed_intro_button_import
|
||||
}
|
||||
|
||||
walletFragment.binding.toolbar.title = walletFragment.getString(titleResId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,691 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.transition.TransitionManager
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import coil.load
|
||||
import com.arkivanov.decompose.defaultComponentContext
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.di.RootAppComponentContext
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.windowsize.rememberWindowSize
|
||||
import com.tangem.datasource.utils.isNullOrEmpty
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.isRing
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
import com.tangem.features.managetokens.component.OnboardingManageTokensComponent
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.features.BaseFragment
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LargeClass", "MagicNumber")
|
||||
@AndroidEntryPoint
|
||||
class OnboardingWalletFragment :
|
||||
BaseFragment(R.layout.fragment_onboarding_wallet),
|
||||
StoreSubscriber<OnboardingWalletState>,
|
||||
FragmentOnBackPressedHandler {
|
||||
|
||||
@Inject
|
||||
internal lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
internal lateinit var onboardingManageTokensComponentFactory: OnboardingManageTokensComponent.Factory
|
||||
|
||||
@Inject
|
||||
@RootAppComponentContext
|
||||
internal lateinit var rootComponentContext: AppComponentContext
|
||||
|
||||
private var onboardingManageTokensComponent: OnboardingManageTokensComponent? = null
|
||||
private lateinit var onboardingComponentContext: AppComponentContext
|
||||
|
||||
internal val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind)
|
||||
internal val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
|
||||
|
||||
internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer }
|
||||
private val bindingManageTokens by lazy { binding.onboardingManageTokensContainer }
|
||||
|
||||
private val canSkipBackup by lazy { arguments?.getBoolean(AppRoute.OnboardingWallet.CAN_SKIP_BACKUP_KEY) ?: true }
|
||||
|
||||
private lateinit var seedPhraseStateHandler: OnboardingSeedPhraseStateHandler
|
||||
|
||||
private val seedPhraseViewModel by viewModels<SeedPhraseViewModel>()
|
||||
|
||||
private lateinit var cardsWidget: WalletCardsWidget
|
||||
private var seedPhraseRouter: SeedPhraseRouter? = null
|
||||
private var accessCodeDialog: AccessCodeDialog? = null
|
||||
|
||||
private lateinit var animator: BackupAnimator
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
seedPhraseStateHandler = OnboardingSeedPhraseStateHandler(activity = requireActivity())
|
||||
|
||||
val newSeedPhraseRouter = makeSeedPhraseRouter()
|
||||
seedPhraseRouter = newSeedPhraseRouter
|
||||
seedPhraseViewModel.setRouter(newSeedPhraseRouter)
|
||||
seedPhraseViewModel.setMediator(makeSeedPhraseMediator())
|
||||
|
||||
onboardingComponentContext = rootComponentContext.childByContext(
|
||||
componentContext = defaultComponentContext(onBackPressedDispatcher = null),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val scaleFactor = getDeviceScaleFactor()
|
||||
initCardsWidget(createLeapfrogWidget(binding.flCardsContainer, scaleFactor), scaleFactor)
|
||||
|
||||
binding.viewPagerBackupInfo.adapter = BackupInfoAdapter()
|
||||
TabLayoutMediator(
|
||||
binding.tabLayoutBackupInfo,
|
||||
binding.viewPagerBackupInfo,
|
||||
) { tab, position ->
|
||||
// Some implementation
|
||||
}.attach()
|
||||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
|
||||
addBackPressHandler(handler = this)
|
||||
|
||||
store.dispatch(OnboardingWallet2Action.Init(seedPhraseViewModel.maxProgress))
|
||||
store.dispatch(OnboardingWalletAction.Init)
|
||||
store.dispatch(
|
||||
OnboardingWalletAction.LoadArtwork(
|
||||
cardArtworkUriForUnfinishedBackup = requireContext().resourceUri(R.drawable.card_placeholder_wallet),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) {
|
||||
val viewBackupCount = flCardsContainer.childCount - 1
|
||||
if (viewBackupCount <= 0) return@with
|
||||
if (viewBackupCount == backupCardsCounts) return@with
|
||||
|
||||
cardsWidget.toFolded(false)
|
||||
|
||||
if (viewBackupCount > backupCardsCounts) {
|
||||
flCardsContainer.removeViews(0, viewBackupCount - backupCardsCounts)
|
||||
} else {
|
||||
flCardsContainer.inflate(R.layout.view_onboarding_card, true)
|
||||
}
|
||||
|
||||
val scaleFactor = getDeviceScaleFactor()
|
||||
initCardsWidget(createLeapfrogWidget(flCardsContainer, scaleFactor), scaleFactor)
|
||||
}
|
||||
|
||||
private fun createLeapfrogWidget(container: FrameLayout, deviceScaleFactor: Float): LeapfrogWidget {
|
||||
val leapfrogCalculator = PropertyCalculator(
|
||||
yTranslationFactor = when (container.childCount) {
|
||||
3 -> 25f
|
||||
else -> 35f
|
||||
} * deviceScaleFactor,
|
||||
)
|
||||
return LeapfrogWidget(container, leapfrogCalculator)
|
||||
}
|
||||
|
||||
private fun initCardsWidget(leapfrogWidget: LeapfrogWidget, deviceScaleFactor: Float, isTest: Boolean = false) {
|
||||
cardsWidget = WalletCardsWidget(leapfrogWidget, deviceScaleFactor)
|
||||
animator = if (isTest) {
|
||||
TestBackupAnimation(WalletBackupAnimator(cardsWidget), binding)
|
||||
} else {
|
||||
WalletBackupAnimator(cardsWidget)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.onboardingWalletState == newState.onboardingWalletState
|
||||
}.select { it.onboardingWalletState }
|
||||
}
|
||||
setStatusBarColor(R.color.background_primary)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
onboardingManageTokensComponent = null
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: OnboardingWalletState) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (activity == null || view == null) return@post
|
||||
animator.updateBackupState(state.backupState)
|
||||
requireActivity().invalidateOptionsMenu()
|
||||
|
||||
pbBinding.pbState.max = state.getMaxProgress()
|
||||
pbBinding.pbState.progress = state.getProgressStep()
|
||||
|
||||
when {
|
||||
state.wallet2State != null -> {
|
||||
seedPhraseStateHandler.newState(this, state, seedPhraseViewModel)
|
||||
seedPhraseViewModel.setCardArtworkUri(
|
||||
cardArtworkUri = state.walletImages.primaryCardImage.toString(),
|
||||
)
|
||||
updateWalletImagesState(state.walletImages)
|
||||
}
|
||||
else -> {
|
||||
updateWalletImagesState(state.walletImages)
|
||||
handleOnboardingStep(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWalletImagesState(walletImages: WalletImages) {
|
||||
if (!walletImages.primaryCardImage.isNullOrEmpty()) {
|
||||
loadImageIntoImageView(walletImages.primaryCardImage, binding.imvFrontCard)
|
||||
}
|
||||
if (!walletImages.secondCardImage.isNullOrEmpty()) {
|
||||
loadImageIntoImageView(walletImages.secondCardImage, binding.imvFirstBackupCard)
|
||||
}
|
||||
if (!walletImages.thirdCardImage.isNullOrEmpty()) {
|
||||
loadImageIntoImageView(walletImages.thirdCardImage, binding.imvSecondBackupCard)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadImageIntoImageView(uri: Uri?, view: ImageView) {
|
||||
view.load(uri) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun handleOnboardingStep(state: OnboardingWalletState) {
|
||||
when (state.step) {
|
||||
OnboardingWalletStep.CreateWallet -> setupCreateWalletState()
|
||||
OnboardingWalletStep.Backup -> setBackupState(state = state.backupState)
|
||||
OnboardingWalletStep.ManageTokens -> setManageTokensState(requireNotNull(state.userWalletId))
|
||||
OnboardingWalletStep.Done -> showSuccess()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeOnboardingManageTokensComponent(userWalletId: UserWalletId) {
|
||||
onboardingManageTokensComponent = onboardingManageTokensComponentFactory.create(
|
||||
context = onboardingComponentContext,
|
||||
params = OnboardingManageTokensComponent.Params(userWalletId = userWalletId),
|
||||
onDone = {},
|
||||
)
|
||||
}
|
||||
|
||||
private fun setManageTokensState(userWalletId: UserWalletId) {
|
||||
with(binding) {
|
||||
tvHeader.show()
|
||||
tvBody.show()
|
||||
viewPagerBackupInfo.hide()
|
||||
tabLayoutBackupInfo.hide()
|
||||
onboardingWalletContainer.show()
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
onboardingWalletContainer.hide()
|
||||
toolbar.title = getText(R.string.onboarding_add_tokens)
|
||||
}
|
||||
pbBinding.pbState.show()
|
||||
bindingSeedPhrase.onboardingSeedPhraseContainer.hide()
|
||||
if (onboardingManageTokensComponent == null) {
|
||||
initializeOnboardingManageTokensComponent(userWalletId)
|
||||
}
|
||||
with(bindingManageTokens) {
|
||||
onboardingManageTokensContainer.hide()
|
||||
onboardingManageTokensContainer.show()
|
||||
onboardingManageTokensContainer.setContent {
|
||||
TangemTheme(
|
||||
isDark = isSystemInDarkTheme(),
|
||||
windowSize = rememberWindowSize(activity = requireActivity()),
|
||||
) {
|
||||
onboardingManageTokensComponent?.Content(modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState() = with(binding) {
|
||||
layoutButtonsCommon.btnWalletMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingWalletAction.CreateWallet)
|
||||
}
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
|
||||
toolbar.title = getText(R.string.onboarding_getting_started)
|
||||
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
animator.setupCreateWalletState()
|
||||
}
|
||||
|
||||
private fun setBackupState(state: BackupState) {
|
||||
when (state.backupStep) {
|
||||
null -> Unit
|
||||
BackupStep.InitBackup -> showBackupIntro(state)
|
||||
BackupStep.ScanOriginCard -> showScanOriginCard()
|
||||
BackupStep.AddBackupCards -> showAddBackupCards(state)
|
||||
BackupStep.SetAccessCode -> showSetAccessCode()
|
||||
BackupStep.EnterAccessCode -> showEnterAccessCode(state)
|
||||
BackupStep.ReenterAccessCode -> showReenterAccessCode(state)
|
||||
is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state)
|
||||
is BackupStep.WriteBackupCard -> showWriteBackupCard(state)
|
||||
BackupStep.Finished -> {
|
||||
// don't need to navigate here"
|
||||
// showSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBackupIntro(state: BackupState) = with(binding) {
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
|
||||
tvHeader.hide()
|
||||
tvBody.hide()
|
||||
viewPagerBackupInfo.show()
|
||||
tabLayoutBackupInfo.show()
|
||||
|
||||
with(layoutButtonsCommon) {
|
||||
btnWalletMainAction.text = getText(R.string.onboarding_button_backup_now)
|
||||
btnWalletMainAction.icon = null
|
||||
btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
|
||||
|
||||
btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) }
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && canSkipBackup)
|
||||
}
|
||||
animator.showBackupIntro(state)
|
||||
}
|
||||
|
||||
private fun showScanOriginCard() = with(binding) {
|
||||
prepareBackupView()
|
||||
tvHeader.text = getText(R.string.onboarding_title_scan_origin_card)
|
||||
tvBody.text = getString(
|
||||
R.string.onboarding_subtitle_scan_primary,
|
||||
)
|
||||
|
||||
with(layoutButtonsCommon) {
|
||||
btnWalletMainAction.text = getString(R.string.onboarding_button_scan_origin_card)
|
||||
btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnWalletAlternativeAction.hide()
|
||||
btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
|
||||
}
|
||||
|
||||
animator.showScanOriginCard()
|
||||
}
|
||||
|
||||
private fun showAddBackupCards(state: BackupState) = with(binding) {
|
||||
prepareBackupView()
|
||||
|
||||
accessCodeDialog?.dismiss()
|
||||
accessCodeDialog = null
|
||||
|
||||
layoutButtonsAddCards.root.show()
|
||||
layoutButtonsCommon.root.hide()
|
||||
layoutButtonsAddCards.btnAddCard.setIconResource(R.drawable.ic_tangem_24)
|
||||
if (state.showBtnLoading) {
|
||||
layoutButtonsAddCards.btnAddCard.iconTint = ColorStateList.valueOf(
|
||||
resources.getColor(R.color.button_secondary),
|
||||
)
|
||||
layoutButtonsAddCards.btnAddCard.text = ""
|
||||
layoutButtonsAddCards.btnAddCard.isEnabled = false
|
||||
layoutButtonsAddCards.btnProgress.show()
|
||||
} else {
|
||||
layoutButtonsAddCards.btnAddCard.iconTint = ColorStateList.valueOf(
|
||||
resources.getColor(R.color.icon_primary_1),
|
||||
)
|
||||
layoutButtonsAddCards.btnProgress.hide()
|
||||
layoutButtonsAddCards.btnAddCard.text = getText(R.string.onboarding_button_add_backup_card)
|
||||
layoutButtonsAddCards.btnAddCard.isEnabled = true
|
||||
}
|
||||
if (state.backupCardsNumber < state.maxBackupCards) {
|
||||
layoutButtonsAddCards.btnAddCard.setOnClickListener {
|
||||
store.dispatch(BackupAction.AddBackupCard)
|
||||
}
|
||||
} else {
|
||||
layoutButtonsAddCards.btnAddCard.isEnabled = false
|
||||
}
|
||||
|
||||
layoutButtonsAddCards.btnContinue.text = getText(R.string.onboarding_button_finalize_backup)
|
||||
layoutButtonsAddCards.btnContinue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
|
||||
layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0
|
||||
when (state.backupCardsNumber) {
|
||||
0 -> {
|
||||
tvHeader.text = getText(R.string.onboarding_title_no_backup_cards)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards)
|
||||
}
|
||||
1 -> {
|
||||
tvHeader.text = getText(R.string.onboarding_title_one_backup_card)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card)
|
||||
}
|
||||
2 -> {
|
||||
tvHeader.text = getText(R.string.onboarding_title_two_backup_cards)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
animator.showAddBackupCards(state, state.backupCardsNumber)
|
||||
}
|
||||
|
||||
private fun prepareBackupView() = with(binding) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup)
|
||||
|
||||
tvHeader.show()
|
||||
tvBody.show()
|
||||
viewPagerBackupInfo.hide()
|
||||
tabLayoutBackupInfo.hide()
|
||||
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
}
|
||||
|
||||
private fun showSetAccessCode() {
|
||||
accessCodeDialog = AccessCodeDialog(requireContext()).apply {
|
||||
dismissWithAnimation = true
|
||||
create()
|
||||
setOnCancelListener {
|
||||
store.dispatch(BackupAction.OnAccessCodeDialogClosed)
|
||||
}
|
||||
|
||||
setOnShowListener { setSecurityMode(true) }
|
||||
|
||||
setOnDismissListener { setSecurityMode(false) }
|
||||
|
||||
show()
|
||||
showInfoScreen()
|
||||
val view =
|
||||
delegate.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
|
||||
(view?.parent as? ViewGroup)?.let { TransitionManager.beginDelayedTransition(it) }
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
view?.let { it.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT }
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEnterAccessCode(state: BackupState) {
|
||||
accessCodeDialog?.showEnterAccessCode()
|
||||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
}
|
||||
|
||||
private fun showReenterAccessCode(state: BackupState) {
|
||||
accessCodeDialog?.showReenterAccessCode()
|
||||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
}
|
||||
|
||||
private fun showWritePrimaryCard(state: BackupState) = with(binding) {
|
||||
accessCodeDialog?.dismiss()
|
||||
|
||||
reInitCardsWidgetIfNeeded(state.backupCardsNumber)
|
||||
prepareViewForFinalizeStep()
|
||||
|
||||
val isRing = state.primaryCardBatchId != null && isRing(batchId = state.primaryCardBatchId)
|
||||
|
||||
tvHeader.text = getText(
|
||||
if (isRing) R.string.common_origin_ring else R.string.common_origin_card,
|
||||
)
|
||||
|
||||
tvBody.text = if (isRing) {
|
||||
getString(R.string.onboarding_subtitle_scan_ring)
|
||||
} else {
|
||||
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
|
||||
getString(
|
||||
R.string.onboarding_subtitle_scan_primary_card_format,
|
||||
state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) },
|
||||
)
|
||||
}
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getText(
|
||||
if (isRing) R.string.onboarding_button_backup_ring else R.string.onboarding_button_backup_origin,
|
||||
)
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
|
||||
|
||||
animator.showWritePrimaryCard(state)
|
||||
}
|
||||
|
||||
private fun prepareViewForFinalizeStep() = with(binding) {
|
||||
toolbar.title = getText(R.string.onboarding_button_finalize_backup)
|
||||
|
||||
layoutButtonsAddCards.root.hide()
|
||||
layoutButtonsCommon.root.show()
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
|
||||
imvCardBackground.hide()
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
}
|
||||
|
||||
private fun showWriteBackupCard(state: BackupState) = with(binding) {
|
||||
reInitCardsWidgetIfNeeded(state.backupCardsNumber)
|
||||
prepareViewForFinalizeStep()
|
||||
|
||||
val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1
|
||||
|
||||
val isRing = isRing(batchId = state.backupCardBatchIds[cardNumber - 1])
|
||||
tvHeader.text = getString(
|
||||
if (isRing) R.string.onboarding_title_backup_ring else R.string.onboarding_title_backup_card,
|
||||
)
|
||||
|
||||
tvBody.text = if (isRing) {
|
||||
getString(R.string.onboarding_subtitle_scan_ring)
|
||||
} else {
|
||||
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
|
||||
getString(
|
||||
R.string.onboarding_subtitle_scan_backup_card_format,
|
||||
cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]),
|
||||
)
|
||||
}
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getString(
|
||||
if (isRing) R.string.onboarding_button_backup_ring else R.string.onboarding_button_backup_origin,
|
||||
)
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
store.dispatch(BackupAction.WriteBackupCard(cardNumber))
|
||||
}
|
||||
|
||||
animator.showWriteBackupCard(state, cardNumber)
|
||||
}
|
||||
|
||||
private fun showSuccess() = with(binding) {
|
||||
toolbar.title = getString(R.string.onboarding_done_header)
|
||||
tvHeader.text = getText(R.string.onboarding_done_header)
|
||||
|
||||
tvHeader.show()
|
||||
tvBody.show()
|
||||
viewPagerBackupInfo.hide()
|
||||
tabLayoutBackupInfo.hide()
|
||||
onboardingWalletContainer.show()
|
||||
bindingManageTokens.onboardingManageTokensContainer.hide()
|
||||
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_continue_wallet)
|
||||
layoutButtonsCommon.btnWalletMainAction.icon = null
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingWalletAction.FinishOnboarding(scope = requireActivity().lifecycleScope))
|
||||
}
|
||||
|
||||
animator.showSuccess {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
flCardsContainer.hide()
|
||||
imvCardBackground.hide()
|
||||
showConfetti(true)
|
||||
imvSuccess.alpha = 0f
|
||||
imvSuccess.show()
|
||||
imvSuccess.animate()?.alpha(1f)?.duration = 400
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun showConfetti(show: Boolean) {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
with(binding.vConfetti) {
|
||||
lavConfetti.show(show)
|
||||
|
||||
if (show) {
|
||||
lavConfetti.playAnimation()
|
||||
} else {
|
||||
lavConfetti.cancelAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
// workaround to use right navigation back for toolbar back btn on seed phrase flow
|
||||
val isWallet2 =
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse?.cardTypesResolver?.isWallet2()
|
||||
?: false
|
||||
val seedPhraseRouter = seedPhraseRouter
|
||||
if (seedPhraseRouter != null && isWallet2) {
|
||||
seedPhraseRouter.navigateBack()
|
||||
} else {
|
||||
legacyOnBackHandler()
|
||||
}
|
||||
}
|
||||
|
||||
private fun legacyOnBackHandler() {
|
||||
store.dispatch(OnboardingWalletAction.OnBackPressed)
|
||||
}
|
||||
|
||||
private fun getDeviceScaleFactor(): Float {
|
||||
val typedValue = TypedValue()
|
||||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
return typedValue.float
|
||||
}
|
||||
|
||||
private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter(
|
||||
onBack = ::legacyOnBackHandler,
|
||||
onOpenChat = {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
|
||||
val scanResponse = requireNotNull(store.state.globalState.onboardingState.onboardingManager?.scanResponse)
|
||||
|
||||
val getCardInfoUseCase = store.inject(DaggerGraphState::getCardInfoUseCase)
|
||||
val cardInfo = requireNotNull(getCardInfoUseCase.invoke(scanResponse).getOrNull())
|
||||
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||
.invoke(type = FeedbackEmailType.DirectUserRequest(cardInfo))
|
||||
}
|
||||
},
|
||||
onOpenUriClick = { uri ->
|
||||
store.dispatchOpenUrl(uri.toString())
|
||||
},
|
||||
)
|
||||
|
||||
private fun makeSeedPhraseMediator(): SeedPhraseMediator {
|
||||
return object : SeedPhraseMediator {
|
||||
override fun createWallet(callback: (CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
store.dispatch(OnboardingWallet2Action.CreateWallet(callback))
|
||||
}
|
||||
|
||||
override fun onWalletCreated(result: CompletionResult<CreateWalletResponse>) {
|
||||
store.dispatch(OnboardingWallet2Action.WalletWasCreated(result))
|
||||
}
|
||||
|
||||
override fun importWallet(
|
||||
mnemonicComponents: List<String>,
|
||||
passphrase: String?,
|
||||
seedPhraseSource: SeedPhraseSource,
|
||||
callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) {
|
||||
store.dispatch(
|
||||
OnboardingWallet2Action.ImportWallet(
|
||||
mnemonicComponents,
|
||||
passphrase,
|
||||
seedPhraseSource,
|
||||
callback,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun allowScreenshots(allow: Boolean) {
|
||||
setSecurityMode(value = !allow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSecurityMode(value: Boolean) {
|
||||
Timber.d("Security mode: ${if (value) "enabled" else "disabled"}")
|
||||
|
||||
if (value) {
|
||||
requireActivity().window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
} else {
|
||||
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.PropertyValuesHolder
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.animation.doOnEnd
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
class WalletCardsWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
private val deviceScaleFactor: Float = 1f,
|
||||
) {
|
||||
|
||||
private val animDuration: Long = 400
|
||||
|
||||
private var currentState: WidgetState? = null
|
||||
|
||||
fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.WELCOME) return
|
||||
|
||||
currentState = WidgetState.WELCOME
|
||||
|
||||
val animator = createAnimator(animate, onEnd = onEnd)
|
||||
animator.playTogether(createAnimators(::createWelcomeProperties))
|
||||
leapfrogWidget.fold { animator.start() }
|
||||
}
|
||||
|
||||
fun toFolded(animate: Boolean = true, duration: Long = animDuration, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.FOLDED) return
|
||||
|
||||
currentState = WidgetState.FOLDED
|
||||
|
||||
val animator = createAnimator(animate, duration) {
|
||||
leapfrogWidget.fold(animate, onEnd)
|
||||
}
|
||||
animator.playTogether(createAnimators(::createLeapfrogProperties))
|
||||
animator.start()
|
||||
}
|
||||
|
||||
fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.FAN) return
|
||||
|
||||
currentState = WidgetState.FAN
|
||||
|
||||
val animator = createAnimator(animate, onEnd = onEnd)
|
||||
animator.playTogether(createAnimators(::createFanProperties))
|
||||
leapfrogWidget.fold { animator.start() }
|
||||
}
|
||||
|
||||
fun toLeapfrog(animate: Boolean = true, onEndFold: () -> Unit = {}, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.LEAPFROG) return
|
||||
|
||||
currentState = WidgetState.LEAPFROG
|
||||
|
||||
val onAnimatorEnd = {
|
||||
leapfrogWidget.fold(animate) {
|
||||
leapfrogWidget.initViews()
|
||||
onEndFold()
|
||||
leapfrogWidget.unfold(animate, onEnd)
|
||||
}
|
||||
}
|
||||
val animator = createAnimator(animate, onEnd = onAnimatorEnd)
|
||||
animator.playTogether(createAnimators(::createLeapfrogProperties))
|
||||
animator.start()
|
||||
}
|
||||
|
||||
private fun createAnimators(propertyFactory: (BackupCardType) -> CardProperties): List<ObjectAnimator> {
|
||||
return getCardTypesTypes().map { createCardAnimator(it, propertyFactory(it)) }
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getCardTypesTypes(): List<BackupCardType> = when (leapfrogWidget.getViewsCount()) {
|
||||
2 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP)
|
||||
3 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP, BackupCardType.SECOND_BACKUP)
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private fun createAnimator(animate: Boolean, duration: Long = animDuration, onEnd: () -> Unit): AnimatorSet {
|
||||
return AnimatorSet().apply {
|
||||
this.duration = if (animate) duration else 0
|
||||
doOnEnd { onEnd() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCardAnimator(cardType: BackupCardType, properties: CardProperties): ObjectAnimator {
|
||||
val view = getLeapViewByCard(cardType).view
|
||||
val animator = ObjectAnimator.ofPropertyValuesHolder(
|
||||
view,
|
||||
*properties.createValuesHolders().toTypedArray(),
|
||||
)
|
||||
view.elevation = properties.elevation
|
||||
return animator
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun createWelcomeProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 440f * deviceScaleFactor,
|
||||
yTranslation = 70f,
|
||||
rotation = 75f,
|
||||
elevation = 2f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 10f * deviceScaleFactor,
|
||||
yTranslation = -30f,
|
||||
rotation = 100f,
|
||||
elevation = 1f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = -440f * deviceScaleFactor,
|
||||
yTranslation = -30f,
|
||||
rotation = 70f,
|
||||
elevation = 0f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun createFanProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 30f,
|
||||
rotation = 5f,
|
||||
elevation = 2f,
|
||||
scale = 1f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = -50f,
|
||||
rotation = -5f,
|
||||
elevation = 1f,
|
||||
scale = 0.9f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = 10f,
|
||||
yTranslation = -120f,
|
||||
rotation = -20f,
|
||||
elevation = 0f,
|
||||
scale = 0.8f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun createLeapfrogProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 2f,
|
||||
scale = 1f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 1f,
|
||||
scale = 0.9f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 0f,
|
||||
scale = 0.8f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
fun getOriginCardView(): ImageView {
|
||||
return getLeapViewByCard(BackupCardType.ORIGIN).view as ImageView
|
||||
}
|
||||
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
fun getFirstBackupCardView(): ImageView {
|
||||
return getLeapViewByCard(BackupCardType.FIRST_BACKUP).view as ImageView
|
||||
}
|
||||
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
fun getSecondBackupCardView(): ImageView {
|
||||
return getLeapViewByCard(BackupCardType.SECOND_BACKUP).view as ImageView
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
private fun getLeapViewByCard(cardType: BackupCardType): LeapView {
|
||||
fun getByIndex(index: Int): LeapView {
|
||||
return leapfrogWidget.getViewByPosition(leapfrogWidget.getViewPositionByIndex(index))
|
||||
}
|
||||
|
||||
return when (leapfrogWidget.getViewsCount()) {
|
||||
2 -> when (cardType) {
|
||||
BackupCardType.ORIGIN -> getByIndex(1)
|
||||
BackupCardType.FIRST_BACKUP -> getByIndex(0)
|
||||
BackupCardType.SECOND_BACKUP -> throw UnsupportedOperationException()
|
||||
}
|
||||
3 -> when (cardType) {
|
||||
BackupCardType.ORIGIN -> getByIndex(2)
|
||||
BackupCardType.FIRST_BACKUP -> getByIndex(1)
|
||||
BackupCardType.SECOND_BACKUP -> getByIndex(0)
|
||||
}
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
enum class WidgetState { WELCOME, FOLDED, FAN, LEAPFROG }
|
||||
}
|
||||
|
||||
enum class BackupCardType { ORIGIN, FIRST_BACKUP, SECOND_BACKUP }
|
||||
|
||||
private data class CardProperties(
|
||||
val xTranslation: Float = 0f,
|
||||
val yTranslation: Float = 0f,
|
||||
val rotation: Float = 0f,
|
||||
val elevation: Float = 0f,
|
||||
val scale: Float = 1.1f,
|
||||
) {
|
||||
|
||||
fun createValuesHolders(): List<PropertyValuesHolder> {
|
||||
return listOf(
|
||||
PropertyValuesHolder.ofFloat(View.TRANSLATION_X, xTranslation),
|
||||
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, yTranslation),
|
||||
PropertyValuesHolder.ofFloat(View.ROTATION, rotation),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_X, scale),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_Y, scale),
|
||||
)
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.AccessCodeError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutBackupAccessCodeBinding
|
||||
|
||||
class AccessCodeDialog(context: Context) : BottomSheetDialog(context) {
|
||||
|
||||
private var binding: LayoutBackupAccessCodeBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = LayoutBackupAccessCodeBinding
|
||||
.inflate(LayoutInflater.from(context))
|
||||
setContentView(binding!!.root)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
|
||||
override fun setOnDismissListener(listener: DialogInterface.OnDismissListener?) {
|
||||
super.setOnDismissListener {
|
||||
listener?.onDismiss(it)
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
||||
fun showInfoScreen() = binding?.run {
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_intro_title)
|
||||
layoutBackupAccessCodeInfo.root.show()
|
||||
layoutBackupAccessCodeSubmit.root.hide()
|
||||
layoutBackupAccessCodeInfo.btnAccessCodeCreate.setOnClickListener {
|
||||
store.dispatch(BackupAction.ShowEnterAccessCodeScreen)
|
||||
}
|
||||
}
|
||||
|
||||
fun showEnterAccessCode() = binding?.run {
|
||||
layoutBackupAccessCodeInfo.root.hide()
|
||||
layoutBackupAccessCodeSubmit.root.show()
|
||||
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_intro_title)
|
||||
|
||||
with(layoutBackupAccessCodeSubmit) {
|
||||
btnAccessCodeSubmit.text = context.getText(R.string.common_continue)
|
||||
btnAccessCodeSubmit.setOnClickListener {
|
||||
store.dispatch(BackupAction.CheckAccessCode(etAccessCode.text.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showReenterAccessCode() = binding?.run {
|
||||
layoutBackupAccessCodeInfo.root.hide()
|
||||
layoutBackupAccessCodeSubmit.root.show()
|
||||
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_repeat_code_title)
|
||||
with(layoutBackupAccessCodeSubmit) {
|
||||
etAccessCode.setText("")
|
||||
btnAccessCodeSubmit.text = context.getText(R.string.common_submit)
|
||||
btnAccessCodeSubmit.setOnClickListener {
|
||||
store.dispatch(BackupAction.SaveAccessCodeConfirmation(etAccessCode.text.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showError(error: AccessCodeError?) = binding?.layoutBackupAccessCodeSubmit?.run {
|
||||
when (error) {
|
||||
AccessCodeError.CodeTooShort ->
|
||||
tilAccessCode.error = context.getString(R.string.onboarding_access_code_too_short)
|
||||
AccessCodeError.CodesDoNotMatch ->
|
||||
tilAccessCode.error = context.getString(R.string.onboarding_access_codes_doesnt_match)
|
||||
null -> tilAccessCode.error = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object AddMoreBackupCardsDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_warning)
|
||||
setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added)
|
||||
setPositiveButton(R.string.common_continue) { _, _ ->
|
||||
store.dispatch(BackupAction.ShowAccessCodeInfoScreen)
|
||||
}
|
||||
setNegativeButton(R.string.common_cancel) { _, _ ->
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal object AttestationFailedDialog {
|
||||
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_error)
|
||||
setMessage(R.string.issuer_signature_loading_failed)
|
||||
setPositiveButton(R.string.common_ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object BackupInProgressDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_warning)
|
||||
setMessage(R.string.onboarding_backup_exit_warning)
|
||||
setPositiveButton(R.string.warning_button_ok) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object InterruptOnboardingDialog {
|
||||
fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(R.string.onboarding_exit_alert_title))
|
||||
setMessage(context.getString(R.string.onboarding_exit_alert_message))
|
||||
setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() }
|
||||
setNegativeButton(R.string.common_cancel) { _, _ -> }
|
||||
setOnDismissListener { store.dispatchDialogHide() }
|
||||
setCancelable(false)
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object ResetBackupCardDialog {
|
||||
fun create(context: Context, cardId: String): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_attention)
|
||||
setMessage(R.string.onboarding_linking_error_card_with_wallets)
|
||||
setPositiveButton(R.string.common_cancel) { _, _ ->
|
||||
Analytics.send(Onboarding.Backup.ResetCancelEvent)
|
||||
}
|
||||
setNegativeButton(R.string.card_settings_action_sheet_reset) { _, _ ->
|
||||
Analytics.send(Onboarding.Backup.ResetPerformEvent)
|
||||
store.dispatch(BackupAction.ResetBackupCard(cardId))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -33,10 +33,6 @@ import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
|
|||
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
|
||||
import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent
|
||||
import com.tangem.tap.features.home.api.HomeComponent
|
||||
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment
|
||||
import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment
|
||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||
|
|
@ -423,11 +419,7 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = nftSendComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.OnboardingNote,
|
||||
is AppRoute.SaveWallet,
|
||||
is AppRoute.OnboardingOther,
|
||||
is AppRoute.OnboardingTwins,
|
||||
is AppRoute.OnboardingWallet,
|
||||
-> error("Unsupported route: $route")
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -545,18 +537,6 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = manageTokensComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.OnboardingNote -> {
|
||||
route.asFragmentChild(Provider { OnboardingNoteFragment() })
|
||||
}
|
||||
is AppRoute.OnboardingOther -> {
|
||||
route.asFragmentChild(Provider { OnboardingOtherCardsFragment() })
|
||||
}
|
||||
is AppRoute.OnboardingTwins -> {
|
||||
route.asFragmentChild(Provider { OnboardingTwinsFragment() })
|
||||
}
|
||||
is AppRoute.OnboardingWallet -> {
|
||||
route.asFragmentChild(Provider { OnboardingWalletFragment() })
|
||||
}
|
||||
is AppRoute.QrScanning -> {
|
||||
val source = when (route.source) {
|
||||
is AppRoute.QrScanning.Source.Send -> SourceType.SEND
|
||||
|
|
|
|||
|
|
@ -40,27 +40,6 @@ sealed class AppRoute(val path: String) : Route {
|
|||
val isTosAccepted: Boolean,
|
||||
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}")
|
||||
|
||||
@Serializable
|
||||
data object OnboardingNote : AppRoute(path = "/onboarding/note")
|
||||
|
||||
@Serializable
|
||||
data class OnboardingWallet(
|
||||
val canSkipBackup: Boolean = true,
|
||||
) : AppRoute(path = "/onboarding/wallet${if (canSkipBackup) "/skippable" else ""}"), RouteBundleParams {
|
||||
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
|
||||
companion object {
|
||||
const val CAN_SKIP_BACKUP_KEY = "canSkipBackup"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data object OnboardingTwins : AppRoute(path = "/onboarding/twins")
|
||||
|
||||
@Serializable
|
||||
data object OnboardingOther : AppRoute(path = "/onboarding/other")
|
||||
|
||||
@Serializable
|
||||
data object Wallet : AppRoute(path = "/wallet")
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
"name": "ONRAMP_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
|
||||
"version": "5.22.0"
|
||||
},
|
||||
{
|
||||
"name": "NAVIGATION_REFACTORING",
|
||||
"version": "5.23.0"
|
||||
|
|
@ -47,10 +43,6 @@
|
|||
"name": "ASK_BIOMETRY_REFACTORING_ENABLED",
|
||||
"version": "5.22.0"
|
||||
},
|
||||
{
|
||||
"name": "NOTE_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "NEW_ARTWORK_LOADING",
|
||||
"version": "5.24.0"
|
||||
|
|
@ -59,10 +51,6 @@
|
|||
"name": "NEW_ATTESTATION_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "TWIN_REFACTORING_ENABLED",
|
||||
"version": "5.23.0"
|
||||
},
|
||||
{
|
||||
"name": "SEND_V2_ENABLED",
|
||||
"version": "5.23.0"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,8 @@
|
|||
package com.tangem.domain.redux
|
||||
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed interface LegacyAction : Action {
|
||||
|
||||
/**
|
||||
* Initiate an onboarding process.
|
||||
* For resuming unfinished backup of standard Wallet see
|
||||
* BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup
|
||||
*/
|
||||
data class StartOnboardingProcess(val scanResponse: ScanResponse, val canSkipBackup: Boolean = true) : LegacyAction
|
||||
|
||||
data object PrepareDetailsScreen : LegacyAction
|
||||
}
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
package com.tangem.features.onboarding.v2
|
||||
|
||||
interface OnboardingV2FeatureToggles {
|
||||
val isOnboardingV2Enabled: Boolean
|
||||
val isVisaOnboardingEnabled: Boolean
|
||||
val isNoteRefactoringEnabled: Boolean
|
||||
val isTwinRefactoringEnabled: Boolean
|
||||
}
|
||||
|
|
@ -6,12 +6,6 @@ import javax.inject.Inject
|
|||
internal class DefaultOnboardingV2FeatureToggles @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : OnboardingV2FeatureToggles {
|
||||
override val isOnboardingV2Enabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("ONBOARDING_CODE_REFACTORING_ENABLED")
|
||||
override val isVisaOnboardingEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("VISA_ONBOARDING_ENABLED")
|
||||
override val isNoteRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("NOTE_REFACTORING_ENABLED")
|
||||
override val isTwinRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("TWIN_REFACTORING_ENABLED")
|
||||
}
|
||||
1
features/onboarding/.gitignore
vendored
1
features/onboarding/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
/build
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.feature.onboarding"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Core modules */
|
||||
implementation(project(":common"))
|
||||
implementation(project(":domain:models"))
|
||||
implementation(project(":core:config-toggles"))
|
||||
implementation(project(":core:datasource"))
|
||||
implementation(project(":core:analytics"))
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(project(":core:utils"))
|
||||
implementation(project(":core:ui"))
|
||||
implementation(project(":core:res"))
|
||||
implementation(project(":data:common"))
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(tangemDeps.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
implementation(deps.androidx.appCompat)
|
||||
implementation(deps.androidx.fragment.ktx)
|
||||
implementation(deps.androidx.constraintLayout)
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.runtime.ktx)
|
||||
implementation(deps.lifecycle.common.java8)
|
||||
implementation(deps.lifecycle.viewModel.ktx)
|
||||
|
||||
/** Compose libraries */
|
||||
implementation(deps.compose.material)
|
||||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.animation)
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.shimmer)
|
||||
|
||||
implementation(tangemDeps.card.core)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.feature.onboarding.api
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface OnboardingSeedPhraseApi {
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
fun ScreenContent(uiState: OnboardingSeedPhraseState, subScreen: SeedPhraseScreen, progress: Float)
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
package com.tangem.feature.onboarding.api
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import com.tangem.core.ui.components.progressbar.TangemLinearProgressIndicator
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingSeedPhraseScreen : OnboardingSeedPhraseApi {
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(uiState: OnboardingSeedPhraseState, subScreen: SeedPhraseScreen, progress: Float) {
|
||||
BackHandler(onBack = uiState.onBackClick)
|
||||
Column {
|
||||
TangemLinearProgressIndicator(
|
||||
progress = { progress },
|
||||
color = TangemTheme.colors.icon.primary1,
|
||||
backgroundColor = TangemTheme.colors.icon.primary1.copy(alpha = 0.4f),
|
||||
strokeCap = StrokeCap.Round,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = TangemTheme.dimens.size8)
|
||||
.padding(horizontal = TangemTheme.dimens.size16),
|
||||
)
|
||||
Content(subScreen, uiState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(screen: SeedPhraseScreen, uiState: OnboardingSeedPhraseState) {
|
||||
when (screen) {
|
||||
SeedPhraseScreen.Intro -> {
|
||||
IntroScreen(
|
||||
state = uiState.introState,
|
||||
)
|
||||
}
|
||||
SeedPhraseScreen.AboutSeedPhrase -> {
|
||||
AboutSeedPhraseScreen(
|
||||
state = uiState.aboutState,
|
||||
)
|
||||
}
|
||||
SeedPhraseScreen.YourSeedPhrase -> {
|
||||
YourSeedPhraseScreen(
|
||||
state = uiState.yourSeedPhraseState,
|
||||
)
|
||||
}
|
||||
SeedPhraseScreen.CheckSeedPhrase -> {
|
||||
CheckSeedPhraseScreen(
|
||||
state = uiState.checkSeedPhraseState,
|
||||
)
|
||||
}
|
||||
SeedPhraseScreen.ImportSeedPhrase -> {
|
||||
ImportSeedPhraseScreen(
|
||||
state = uiState.importSeedPhraseState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
package com.tangem.feature.onboarding.data
|
||||
|
||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.bip39.EntropyLength
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
import com.tangem.feature.onboarding.domain.models.MnemonicType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class DefaultMnemonicRepository(
|
||||
private val bip39Wordlist: Wordlist,
|
||||
) : MnemonicRepository {
|
||||
|
||||
private val bip39Words: Set<String> by lazy { bip39Wordlist.words.toHashSet() }
|
||||
|
||||
override fun getWordsDictionary(): Set<String> = bip39Words
|
||||
|
||||
override fun generateDefaultMnemonic(): Mnemonic {
|
||||
return DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist)
|
||||
}
|
||||
|
||||
override fun generateMnemonic(mnemonicType: MnemonicType): Mnemonic {
|
||||
return when (mnemonicType) {
|
||||
MnemonicType.Mnemonic12 -> DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist)
|
||||
MnemonicType.Mnemonic24 -> DefaultMnemonic(EntropyLength.Bits256Length, bip39Wordlist)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist)
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.feature.onboarding.data
|
||||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.feature.onboarding.domain.models.MnemonicType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface MnemonicRepository {
|
||||
|
||||
fun getWordsDictionary(): Set<String>
|
||||
|
||||
@Throws(TangemSdkError.MnemonicException::class)
|
||||
fun generateDefaultMnemonic(): Mnemonic
|
||||
|
||||
@Throws(TangemSdkError.MnemonicException::class)
|
||||
fun generateMnemonic(mnemonicType: MnemonicType): Mnemonic
|
||||
|
||||
@Throws(TangemSdkError.MnemonicException::class)
|
||||
fun createMnemonic(mnemonicString: String): Mnemonic
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.feature.onboarding.data.model
|
||||
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class CreateWalletResponse(
|
||||
val card: CardDTO,
|
||||
val derivedKeys: Map<ByteArrayKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null,
|
||||
)
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.feature.onboarding.di
|
||||
|
||||
import com.tangem.feature.onboarding.navigation.DefaultOnboardingRouter
|
||||
import com.tangem.feature.onboarding.navigation.OnboardingRouter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
internal object OnboardingRouterModule {
|
||||
|
||||
@Provides
|
||||
@ActivityScoped
|
||||
fun provideOnboardingRouter(): OnboardingRouter {
|
||||
return DefaultOnboardingRouter()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
package com.tangem.feature.onboarding.domain
|
||||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.crypto.bip39.MnemonicErrorResult
|
||||
import com.tangem.feature.onboarding.data.MnemonicRepository
|
||||
import com.tangem.feature.onboarding.domain.models.MnemonicType
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField
|
||||
import com.tangem.utils.extensions.isNotWhitespace
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultSeedPhraseInteractor(
|
||||
private val repository: MnemonicRepository,
|
||||
) : SeedPhraseInteractor {
|
||||
|
||||
private val partWordFinder: PartWordFinder = PartWordFinder()
|
||||
|
||||
override suspend fun generateMnemonic(): Result<Mnemonic> {
|
||||
return runCatching {
|
||||
repository.generateDefaultMnemonic()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun generateMnemonics(mnemonicTypes: List<MnemonicType>): Result<Map<MnemonicType, Mnemonic>> {
|
||||
return runCatching {
|
||||
mnemonicTypes.associateWith {
|
||||
repository.generateMnemonic(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isWordMatch(mnemonicComponents: List<String>?, field: SeedPhraseField, word: String): Boolean {
|
||||
return mnemonicComponents?.get(field.index) == word
|
||||
}
|
||||
|
||||
override suspend fun validateMnemonicString(text: String): Result<List<String>> {
|
||||
val inputWords = text.split("\\s+".toRegex()).toSet()
|
||||
val wordsDictionary = repository.getWordsDictionary()
|
||||
|
||||
val invalidWords = inputWords
|
||||
.filter { it.isNotEmpty() && !wordsDictionary.contains(it) }
|
||||
.toSet()
|
||||
|
||||
return if (invalidWords.isNotEmpty()) {
|
||||
Result.failure(SeedPhraseError.InvalidWords(invalidWords))
|
||||
} else {
|
||||
try {
|
||||
val mnemonic = repository.createMnemonic(text)
|
||||
Result.success(mnemonic.mnemonicComponents)
|
||||
} catch (ex: TangemSdkError.MnemonicException) {
|
||||
val error = ex.mnemonicResult.mapToError()
|
||||
Result.failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSuggestions(
|
||||
text: String,
|
||||
hasSelection: Boolean,
|
||||
cursorPosition: Int,
|
||||
): ImmutableList<String> {
|
||||
if (text.isEmpty() || cursorPosition == 0 || hasSelection) return persistentListOf()
|
||||
val word = partWordFinder.getLeadPartOfWord(text, cursorPosition)
|
||||
?: return persistentListOf()
|
||||
|
||||
val suggestions = repository.getWordsDictionary()
|
||||
.filter { it.startsWith(word, ignoreCase = false) && it != word }
|
||||
.toPersistentList()
|
||||
|
||||
return suggestions
|
||||
}
|
||||
|
||||
override suspend fun insertSuggestionWord(
|
||||
text: String,
|
||||
suggestion: String,
|
||||
cursorPosition: Int,
|
||||
): InsertSuggestionResult {
|
||||
val textPartLead = text.substring(0, cursorPosition)
|
||||
val textPartLast = text.substring(cursorPosition, text.length)
|
||||
val wordPartLast = partWordFinder.getLastPartOfWord(text, suggestion, cursorPosition)
|
||||
|
||||
val textBuilder = StringBuilder()
|
||||
.append(textPartLead)
|
||||
.append(wordPartLast)
|
||||
|
||||
if (textPartLast.isEmpty() || textPartLast[0].isNotWhitespace()) {
|
||||
textBuilder.append(SeedPhraseInteractor.MNEMONIC_DELIMITER)
|
||||
}
|
||||
textBuilder.append(textPartLast)
|
||||
|
||||
val textWithSuggestion = textBuilder.toString()
|
||||
val newCursorPosition = cursorPosition + wordPartLast.length + SeedPhraseInteractor.MNEMONIC_DELIMITER.length
|
||||
|
||||
return InsertSuggestionResult(textWithSuggestion, newCursorPosition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MnemonicErrorResult.mapToError(): SeedPhraseError = when (this) {
|
||||
MnemonicErrorResult.InvalidWordCount -> SeedPhraseError.InvalidWordCount
|
||||
MnemonicErrorResult.InvalidEntropyLength -> SeedPhraseError.InvalidEntropyLength
|
||||
MnemonicErrorResult.InvalidWordsFile -> SeedPhraseError.InvalidWordsFile
|
||||
MnemonicErrorResult.InvalidChecksum -> SeedPhraseError.InvalidChecksum
|
||||
MnemonicErrorResult.MnenmonicCreationFailed -> SeedPhraseError.MnenmonicCreationFailed
|
||||
MnemonicErrorResult.NormalizationFailed -> SeedPhraseError.NormalizationFailed
|
||||
MnemonicErrorResult.UnsupportedLanguage -> SeedPhraseError.UnsupportedLanguage
|
||||
is MnemonicErrorResult.InvalidWords -> SeedPhraseError.InvalidWords(this.words)
|
||||
MnemonicErrorResult.InvalidMnemonic -> SeedPhraseError.InvalidMnemonic
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package com.tangem.feature.onboarding.domain
|
||||
|
||||
import com.tangem.common.module.ModuleError
|
||||
import com.tangem.common.module.ModuleErrorCode
|
||||
import com.tangem.common.module.ModuleMessage
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
* For each pool of errors, use the codes lying in range 100 (200..299, 300..399, etc)
|
||||
*/
|
||||
abstract class OnboardingModuleError(
|
||||
subCode: Int,
|
||||
override val message: String,
|
||||
override val data: Any?,
|
||||
) : ModuleMessage, ModuleError() {
|
||||
override val code: Int = ModuleErrorCode.ONBOARDING + subCode
|
||||
|
||||
companion object {
|
||||
internal const val ERROR_CODE_SEED_PHRASE = 100
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SeedPhraseError(
|
||||
subCode: Int = 0,
|
||||
message: String? = null,
|
||||
data: Any? = null,
|
||||
) : OnboardingModuleError(
|
||||
subCode = ERROR_CODE_SEED_PHRASE + subCode,
|
||||
message = message ?: this::class.java.simpleName,
|
||||
data = data,
|
||||
) {
|
||||
object InvalidWordCount : SeedPhraseError(subCode = 0)
|
||||
object InvalidEntropyLength : SeedPhraseError(subCode = 1)
|
||||
object InvalidWordsFile : SeedPhraseError(subCode = 2)
|
||||
object InvalidChecksum : SeedPhraseError(subCode = 3)
|
||||
object MnenmonicCreationFailed : SeedPhraseError(subCode = 4)
|
||||
object NormalizationFailed : SeedPhraseError(subCode = 5)
|
||||
object UnsupportedLanguage : SeedPhraseError(subCode = 6)
|
||||
data class InvalidWords(val words: Set<String>) : SeedPhraseError(subCode = 7)
|
||||
object InvalidMnemonic : SeedPhraseError(subCode = 8)
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package com.tangem.feature.onboarding.domain
|
||||
|
||||
import com.tangem.utils.extensions.isNotWhitespace
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
* Class which provides helper functions to determine lead or last part of words for suggestions
|
||||
*/
|
||||
internal class PartWordFinder {
|
||||
|
||||
/**
|
||||
* Searches and return part of a word on the left side of the [cursorPosition] into the [text]
|
||||
*/
|
||||
fun getLeadPartOfWord(text: String, cursorPosition: Int): String? {
|
||||
val charText = text.toCharArray().asList()
|
||||
val cursorIsOutOfTextRange = cursorPosition > charText.size || cursorPosition < 0
|
||||
val cursorAtStartOfText = cursorPosition == 0
|
||||
val hadWhiteSpaceOnLeftOfCursor = charText[cursorPosition - 1].isWhitespace()
|
||||
val cursorInMiddleOfWord = charText.size > cursorPosition && charText[cursorPosition].isNotWhitespace()
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
if (cursorIsOutOfTextRange || cursorAtStartOfText || hadWhiteSpaceOnLeftOfCursor || cursorInMiddleOfWord) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (charIndex in cursorPosition - 1 downTo 0) {
|
||||
val char = charText[charIndex]
|
||||
when {
|
||||
charIndex == 0 -> {
|
||||
return text.substring(charIndex, cursorPosition)
|
||||
}
|
||||
char.isWhitespace() -> {
|
||||
return text.substring(charIndex + 1, cursorPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a substring of the [word] which excludes the first word part found on the left of [cursorPosition] in
|
||||
* the [text]
|
||||
*/
|
||||
fun getLastPartOfWord(text: String, word: String, cursorPosition: Int): String {
|
||||
val leadPart = getLeadPartOfWord(text, cursorPosition) ?: return ""
|
||||
|
||||
return word.substring(leadPart.length, word.length)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package com.tangem.feature.onboarding.domain
|
||||
|
||||
import com.tangem.crypto.bip39.Mnemonic
|
||||
import com.tangem.feature.onboarding.domain.models.MnemonicType
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SeedPhraseInteractor {
|
||||
suspend fun generateMnemonic(): Result<Mnemonic>
|
||||
suspend fun generateMnemonics(mnemonicTypes: List<MnemonicType>): Result<Map<MnemonicType, Mnemonic>>
|
||||
suspend fun isWordMatch(mnemonicComponents: List<String>?, field: SeedPhraseField, word: String): Boolean
|
||||
suspend fun validateMnemonicString(text: String): Result<List<String>>
|
||||
suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): ImmutableList<String>
|
||||
suspend fun insertSuggestionWord(text: String, suggestion: String, cursorPosition: Int): InsertSuggestionResult
|
||||
|
||||
companion object {
|
||||
const val MNEMONIC_DELIMITER = " "
|
||||
}
|
||||
}
|
||||
|
||||
data class InsertSuggestionResult(
|
||||
val text: String,
|
||||
val cursorPosition: Int,
|
||||
)
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.feature.onboarding.domain.di
|
||||
|
||||
import com.tangem.feature.onboarding.data.MnemonicRepository
|
||||
import com.tangem.feature.onboarding.domain.DefaultSeedPhraseInteractor
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseInteractor
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class OnboardingDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSeedPhraseInteractor(repository: MnemonicRepository): SeedPhraseInteractor {
|
||||
return DefaultSeedPhraseInteractor(repository)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.tangem.feature.onboarding.domain.models
|
||||
|
||||
enum class MnemonicType {
|
||||
Mnemonic12, Mnemonic24;
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
package com.tangem.feature.onboarding.navigation
|
||||
|
||||
class DefaultOnboardingRouter : OnboardingRouter
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package com.tangem.feature.onboarding.navigation
|
||||
|
||||
/**
|
||||
* Onboarding router
|
||||
*/
|
||||
// TODO: Move to onboarding api module [REDACTED_JIRA]
|
||||
interface OnboardingRouter
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
sealed class SeedPhraseEvents(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(ONBOARDING_SEED_PHRASE, event, params) {
|
||||
|
||||
object ButtonGenerateSeedPhrase : SeedPhraseEvents("Button - Generate Seed Phrase")
|
||||
|
||||
object ButtonImportWallet : SeedPhraseEvents("Button - Import Wallet")
|
||||
object ButtonReadMore : SeedPhraseEvents("Button - Read More")
|
||||
object ButtonImport : SeedPhraseEvents("Button - Import")
|
||||
|
||||
object IntroScreenOpened : SeedPhraseEvents("Seed Intro Screen Opened")
|
||||
object GenerationScreenOpened : SeedPhraseEvents("Seed Generation Screen Opened")
|
||||
object CheckingScreenOpened : SeedPhraseEvents("Seed Checking Screen Opened")
|
||||
object ImportScreenOpened : SeedPhraseEvents("Import Seed Phrase Screen Opened")
|
||||
|
||||
companion object {
|
||||
const val ONBOARDING_SEED_PHRASE = "Onboarding / Seed Phrase"
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CreateWalletEvents(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(ONBOARDING_CREATE_WALLET, event, params) {
|
||||
|
||||
object OnboardingSeedButtonCreateWallet : CreateWalletEvents("Button - Create Wallet")
|
||||
|
||||
object OnboardingSeedButtonOtherOptions : CreateWalletEvents("Button - Other Options")
|
||||
companion object {
|
||||
const val ONBOARDING_CREATE_WALLET = "Onboarding / Create Wallet"
|
||||
}
|
||||
}
|
||||
|
||||
enum class SeedPhraseSource {
|
||||
IMPORTED, GENERATED
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.model
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class ButtonState(
|
||||
val enabled: Boolean = true,
|
||||
val isClickable: Boolean = true,
|
||||
val showProgress: Boolean = false,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
data class DescriptionResource(
|
||||
@StringRes val titleRes: Int,
|
||||
@StringRes val subTitleRes: Int,
|
||||
)
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.model
|
||||
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseError
|
||||
import com.tangem.feature.onboarding.domain.models.MnemonicType
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingSeedPhraseState(
|
||||
val introState: IntroState,
|
||||
val aboutState: AboutState,
|
||||
val yourSeedPhraseState: YourSeedPhraseState,
|
||||
val checkSeedPhraseState: CheckSeedPhraseState,
|
||||
val importSeedPhraseState: ImportSeedPhraseState,
|
||||
val menuButtonChat: ButtonState,
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class IntroState(
|
||||
val cardImageUrl: String? = null,
|
||||
val buttonCreateWallet: ButtonState,
|
||||
val buttonOtherOptions: ButtonState,
|
||||
)
|
||||
|
||||
data class AboutState(
|
||||
val buttonReadMoreAboutSeedPhrase: ButtonState,
|
||||
val buttonGenerateSeedPhrase: ButtonState,
|
||||
val buttonImportSeedPhrase: ButtonState,
|
||||
)
|
||||
|
||||
data class YourSeedPhraseState(
|
||||
val segmentSeedState: SegmentSeedState,
|
||||
val mnemonicGridItems: PersistentList<MnemonicGridItem> = persistentListOf(),
|
||||
val buttonContinue: ButtonState,
|
||||
)
|
||||
|
||||
data class SegmentSeedState(
|
||||
val seedSegments: PersistentList<SegmentSeedType>,
|
||||
val selectedSeedType: SegmentSeedType,
|
||||
val onSelectType: (SegmentSeedType) -> Unit,
|
||||
)
|
||||
|
||||
enum class SegmentSeedType(val count: Int) {
|
||||
SEED_12(count = 12), SEED_24(count = 24);
|
||||
|
||||
fun toMnemonicType(): MnemonicType {
|
||||
return when (this) {
|
||||
SEED_12 -> MnemonicType.Mnemonic12
|
||||
SEED_24 -> MnemonicType.Mnemonic24
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromMnemonicType(mnemonicType: MnemonicType): SegmentSeedType {
|
||||
return when (mnemonicType) {
|
||||
MnemonicType.Mnemonic12 -> SEED_12
|
||||
MnemonicType.Mnemonic24 -> SEED_24
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MnemonicGridItem(
|
||||
val index: Int,
|
||||
val mnemonic: String,
|
||||
)
|
||||
|
||||
data class CheckSeedPhraseState(
|
||||
val tvSecondPhrase: TextFieldState,
|
||||
val tvSeventhPhrase: TextFieldState,
|
||||
val tvEleventhPhrase: TextFieldState,
|
||||
val buttonCreateWallet: ButtonState,
|
||||
)
|
||||
|
||||
data class ImportSeedPhraseState(
|
||||
val fieldSeedPhrase: TextFieldState,
|
||||
val fieldPassphrase: TextFieldState,
|
||||
val onSuggestedPhraseClick: (Int) -> Unit,
|
||||
val onPassphraseInfoClick: () -> Unit,
|
||||
val buttonCreateWallet: ButtonState,
|
||||
val invalidWords: Set<String> = emptySet(),
|
||||
val suggestionsList: ImmutableList<String> = persistentListOf(),
|
||||
val error: SeedPhraseError? = null,
|
||||
val bottomSheetConfig: TangemBottomSheetConfig? = null,
|
||||
)
|
||||
|
||||
data class ShowPassphraseInfoBottomSheetContent(val onOkClick: () -> Unit) : TangemBottomSheetConfigContent
|
||||
|
||||
data class TextFieldState(
|
||||
val onTextFieldValueChanged: (TextFieldValue) -> Unit,
|
||||
val textFieldValue: TextFieldValue = TextFieldValue(),
|
||||
val label: String? = null,
|
||||
val isError: Boolean = false,
|
||||
val isFocused: Boolean = false,
|
||||
val onFocusChanged: (Boolean) -> Unit = {},
|
||||
)
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.model
|
||||
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class UiActions(
|
||||
val introActions: IntroUiAction,
|
||||
val aboutActions: AboutUiAction,
|
||||
val yourSeedPhraseActions: YourSeedPhraseUiAction,
|
||||
val checkSeedPhraseActions: CheckSeedPhraseUiAction,
|
||||
val importSeedPhraseActions: ImportSeedPhraseUiAction,
|
||||
val menuChatClick: () -> Unit,
|
||||
val menuNavigateBackClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class IntroUiAction(
|
||||
val buttonCreateWalletClick: () -> Unit,
|
||||
val buttonOtherOptionsClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class AboutUiAction(
|
||||
val buttonReadMoreAboutSeedPhraseClick: () -> Unit,
|
||||
val buttonGenerateSeedPhraseClick: () -> Unit,
|
||||
val buttonImportSeedPhraseClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class YourSeedPhraseUiAction(
|
||||
val buttonContinueClick: () -> Unit,
|
||||
val onSelectType: (SegmentSeedType) -> Unit,
|
||||
)
|
||||
|
||||
data class CheckSeedPhraseUiAction(
|
||||
val secondTextFieldAction: TextFieldUiAction,
|
||||
val seventhTextFieldAction: TextFieldUiAction,
|
||||
val eleventhTextFieldAction: TextFieldUiAction,
|
||||
val buttonCreateWalletClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class ImportSeedPhraseUiAction(
|
||||
val phraseTextFieldAction: TextFieldUiAction,
|
||||
val passTextFieldAction: TextFieldUiAction,
|
||||
val suggestedPhraseClick: (Int) -> Unit,
|
||||
val onPassphraseInfoClick: () -> Unit,
|
||||
val buttonCreateWalletClick: () -> Unit,
|
||||
)
|
||||
|
||||
data class TextFieldUiAction(
|
||||
val onTextFieldChanged: (TextFieldValue) -> Unit,
|
||||
val onFocusChanged: (Boolean) -> Unit = {},
|
||||
)
|
||||
|
||||
|
||||
|
||||
enum class SeedPhraseField(val index: Int) {
|
||||
Second(index = 1),
|
||||
Seventh(index = 6),
|
||||
Eleventh(index = 10),
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.AboutState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun AboutSeedPhraseScreen(state: AboutState, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(weight = 0.8f),
|
||||
) {
|
||||
EditIconBlock()
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.weight(weight = 1.2f),
|
||||
) {
|
||||
Column {
|
||||
OnboardingDescriptionBlock {
|
||||
Description(
|
||||
titleRes = R.string.onboarding_seed_intro_title,
|
||||
subTitleRes = R.string.onboarding_seed_intro_message,
|
||||
)
|
||||
}
|
||||
ReadMoreBlock(state)
|
||||
}
|
||||
}
|
||||
Box {
|
||||
OnboardingActionBlock(
|
||||
firstActionContent = {
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.onboarding_seed_intro_button_generate),
|
||||
enabled = state.buttonGenerateSeedPhrase.enabled,
|
||||
showProgress = state.buttonGenerateSeedPhrase.showProgress,
|
||||
onClick = state.buttonGenerateSeedPhrase.onClick,
|
||||
)
|
||||
},
|
||||
secondActionContent = {
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.onboarding_seed_intro_button_import),
|
||||
enabled = state.buttonImportSeedPhrase.enabled,
|
||||
showProgress = state.buttonImportSeedPhrase.showProgress,
|
||||
onClick = state.buttonImportSeedPhrase.onClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EditIconBlock() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size44),
|
||||
painter = painterResource(id = R.drawable.ic_onboarding_text_edit_56),
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerH32()
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.warning.copy(alpha = 0.12f),
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius8),
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.size12,
|
||||
vertical = TangemTheme.dimens.size4,
|
||||
),
|
||||
text = stringResourceSafe(id = R.string.onboarding_seed_phrase_intro_legacy),
|
||||
color = TangemTheme.colors.text.warning,
|
||||
style = TangemTheme.typography.body1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReadMoreBlock(state: AboutState) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens.size16),
|
||||
) {
|
||||
val shape = TangemTheme.shapes.roundedCornersLarge
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.clip(shape)
|
||||
.border(
|
||||
width = TangemTheme.dimens.size1,
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
shape = shape,
|
||||
)
|
||||
.clickable(onClick = state.buttonReadMoreAboutSeedPhrase.onClick)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.size16,
|
||||
vertical = TangemTheme.dimens.size8,
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_arrow_top_right_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
)
|
||||
SpacerW8()
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.onboarding_seed_button_read_more),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.OutlineTextField
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.fields.contextmenu.DisableContextMenu
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("ReusedModifierInstance")
|
||||
@Composable
|
||||
fun CheckSeedPhraseScreen(state: CheckSeedPhraseState, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
item {
|
||||
OnboardingDescriptionBlock(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(top = TangemTheme.dimens.size48)
|
||||
.padding(horizontal = TangemTheme.dimens.size16),
|
||||
) {
|
||||
Description(
|
||||
titleRes = R.string.onboarding_seed_user_validation_title,
|
||||
subTitleRes = R.string.onboarding_seed_user_validation_message,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
CheckSeedPhraseBlock(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.weight(1f)
|
||||
.padding(top = TangemTheme.dimens.size20)
|
||||
.padding(horizontal = TangemTheme.dimens.size16),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
OnboardingActionBlock(
|
||||
firstActionContent = {
|
||||
PrimaryButtonIconEnd(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.onboarding_create_wallet_button_create_wallet),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
enabled = state.buttonCreateWallet.enabled,
|
||||
showProgress = state.buttonCreateWallet.showProgress,
|
||||
onClick = state.buttonCreateWallet.onClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CheckSeedPhraseBlock(state: CheckSeedPhraseState, modifier: Modifier = Modifier) {
|
||||
Column(modifier) {
|
||||
CheckSeedPhraseTextField(state = state.tvSecondPhrase, imeAction = ImeAction.Next)
|
||||
CheckSeedPhraseTextField(state = state.tvSeventhPhrase, imeAction = ImeAction.Next)
|
||||
CheckSeedPhraseTextField(state = state.tvEleventhPhrase, imeAction = ImeAction.Done)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CheckSeedPhraseTextField(state: TextFieldState, imeAction: ImeAction) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
DisableContextMenu {
|
||||
OutlineTextField(
|
||||
value = state.textFieldValue,
|
||||
onValueChange = state.onTextFieldValueChanged,
|
||||
label = state.label,
|
||||
isError = state.isError,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
imeAction = imeAction,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = { focusManager.clearFocus() },
|
||||
onNext = { focusManager.moveFocus(focusDirection = FocusDirection.Down) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,268 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionSubTitleText
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.InvalidWordsColorTransformation
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseErrorConverter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Modifier) {
|
||||
val keyboard by keyboardAsState()
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
Column {
|
||||
OnboardingDescriptionBlock(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.size16),
|
||||
) {
|
||||
DescriptionSubTitleText(text = stringResourceSafe(id = R.string.onboarding_seed_import_message))
|
||||
}
|
||||
PhraseBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.size62)
|
||||
.padding(horizontal = TangemTheme.dimens.size16),
|
||||
state = state,
|
||||
)
|
||||
|
||||
PassphraseBlock(
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.size16),
|
||||
passphraseField = state.fieldPassphrase,
|
||||
onPassphraseInfoClick = state.onPassphraseInfoClick,
|
||||
)
|
||||
|
||||
OnboardingActionBlock(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.size16),
|
||||
firstActionContent = {
|
||||
PrimaryButtonIconEnd(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.common_import),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
enabled = state.buttonCreateWallet.enabled,
|
||||
showProgress = state.buttonCreateWallet.showProgress,
|
||||
onClick = state.buttonCreateWallet.onClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (keyboard is Keyboard.Opened) {
|
||||
SuggestionsBlock(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.imePadding(),
|
||||
suggestionsList = state.suggestionsList,
|
||||
onClick = { index -> state.onSuggestedPhraseClick(index) },
|
||||
)
|
||||
}
|
||||
|
||||
if (state.bottomSheetConfig != null) {
|
||||
PassphraseInfoBottomSheet(config = state.bottomSheetConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PassphraseBlock(
|
||||
passphraseField: TextFieldState,
|
||||
onPassphraseInfoClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
OutlineTextFieldWithIcon(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
value = passphraseField.textFieldValue,
|
||||
onValueChange = passphraseField.onTextFieldValueChanged,
|
||||
iconResId = R.drawable.ic_information_24,
|
||||
iconColor = TangemTheme.colors.icon.informative,
|
||||
label = stringResourceSafe(id = R.string.common_passphrase),
|
||||
placeholder = stringResourceSafe(id = R.string.send_optional_field),
|
||||
onIconClick = onPassphraseInfoClick,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modifier) {
|
||||
// TODO: replace by TextReference
|
||||
val errorConverter = remember { SeedPhraseErrorConverter() }
|
||||
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size142),
|
||||
value = state.fieldSeedPhrase.textFieldValue,
|
||||
onValueChange = state.fieldSeedPhrase.onTextFieldValueChanged,
|
||||
textStyle = TangemTheme.typography.body1,
|
||||
singleLine = false,
|
||||
visualTransformation = InvalidWordsColorTransformation(
|
||||
wordsToBrush = state.invalidWords,
|
||||
style = SpanStyle(color = TangemTheme.colors.text.warning),
|
||||
),
|
||||
colors = TangemTextFieldsDefault.defaultTextFieldColors,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
autoCorrectEnabled = false,
|
||||
keyboardType = KeyboardType.Password,
|
||||
),
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size32),
|
||||
) {
|
||||
val message = errorConverter.convert(LocalContext.current to state.error)
|
||||
if (message != null) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
text = message,
|
||||
style = TangemTheme.typography.caption2.copy(
|
||||
color = TangemTheme.colors.text.warning,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ReusedModifierInstance")
|
||||
@Composable
|
||||
private fun SuggestionsBlock(
|
||||
suggestionsList: ImmutableList<String>,
|
||||
onClick: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
modifier = modifier,
|
||||
enter = fadeIn() + slideIn(initialOffset = { IntOffset(x = 200, y = 0) }),
|
||||
exit = slideOut(targetOffset = { IntOffset(x = -200, y = 0) }) + fadeOut(),
|
||||
visible = suggestionsList.isNotEmpty(),
|
||||
) {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16),
|
||||
) {
|
||||
items(suggestionsList.size) { index ->
|
||||
SuggestionButton(
|
||||
modifier = Modifier.rowPadding(
|
||||
index = index,
|
||||
rowSize = suggestionsList.size,
|
||||
outSide = TangemTheme.dimens.size0,
|
||||
inSide = TangemTheme.dimens.size4,
|
||||
),
|
||||
text = suggestionsList[index],
|
||||
onClick = { onClick(index) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SuggestionButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Box(modifier = modifier.clickable(onClick = onClick)) {
|
||||
Notifier(
|
||||
text = text,
|
||||
backgroundColor = TangemTheme.colors.icon.primary1,
|
||||
textColor = TangemTheme.colors.text.primary2,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: Dp): Modifier = when (index) {
|
||||
0 -> this.padding(start = outSide, end = inSide)
|
||||
rowSize - 1 -> this.padding(start = inSide, end = outSide)
|
||||
else -> this.padding(horizontal = inSide)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun SuggestionsBlockPreview(
|
||||
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
SuggestionsBlock(
|
||||
suggestionsList = suggestions,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun ImportSeedPhraseScreenPreview(
|
||||
@PreviewParameter(SuggestionsPreviewParamsProvider::class) suggestions: ImmutableList<String>,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
Box(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
|
||||
ImportSeedPhraseScreen(
|
||||
ImportSeedPhraseState(
|
||||
fieldSeedPhrase = TextFieldState(onTextFieldValueChanged = {}),
|
||||
fieldPassphrase = TextFieldState(onTextFieldValueChanged = {}),
|
||||
onSuggestedPhraseClick = {},
|
||||
onPassphraseInfoClick = {},
|
||||
buttonCreateWallet = ButtonState(
|
||||
enabled = true,
|
||||
isClickable = true,
|
||||
showProgress = false,
|
||||
onClick = {},
|
||||
),
|
||||
suggestionsList = suggestions,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionsPreviewParamsProvider : CollectionPreviewParameterProvider<ImmutableList<String>>(
|
||||
collection = listOf(
|
||||
persistentListOf(
|
||||
"one",
|
||||
"each",
|
||||
"explorer",
|
||||
"unknown",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.SecondaryButton
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.IntroState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun IntroScreen(state: IntroState, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(weight = 1.5f),
|
||||
) {
|
||||
CardImageBlock(state.cardImageUrl)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
) {
|
||||
OnboardingDescriptionBlock {
|
||||
Description(
|
||||
titleRes = R.string.onboarding_create_wallet_options_title,
|
||||
subTitleRes = R.string.onboarding_create_wallet_options_message,
|
||||
)
|
||||
}
|
||||
}
|
||||
Box {
|
||||
OnboardingActionBlock(
|
||||
firstActionContent = {
|
||||
PrimaryButtonIconEnd(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.onboarding_create_wallet_button_create_wallet),
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
enabled = state.buttonCreateWallet.enabled,
|
||||
showProgress = state.buttonCreateWallet.showProgress,
|
||||
onClick = state.buttonCreateWallet.onClick,
|
||||
)
|
||||
},
|
||||
secondActionContent = {
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.onboarding_create_wallet_options_button_options),
|
||||
enabled = state.buttonOtherOptions.enabled,
|
||||
showProgress = state.buttonOtherOptions.showProgress,
|
||||
onClick = state.buttonOtherOptions.onClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CardImageBlock(cardImageUrl: String? = null) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
val cardImageModifier = Modifier
|
||||
.fillMaxSize()
|
||||
.align(Alignment.Center)
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = cardImageModifier.padding(horizontal = TangemTheme.dimens.size34),
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.data(cardImageUrl)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
loading = { CardPlaceHolder(cardImageModifier) },
|
||||
error = { CardPlaceHolder(cardImageModifier) },
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CardPlaceHolder(modifier: Modifier = Modifier) {
|
||||
Image(
|
||||
modifier = modifier,
|
||||
painter = painterResource(id = R.drawable.card_placeholder_black),
|
||||
contentScale = ContentScale.Fit,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ShowPassphraseInfoBottomSheetContent
|
||||
|
||||
@Composable
|
||||
fun PassphraseInfoBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet(
|
||||
config = config,
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
) { content: ShowPassphraseInfoBottomSheetContent ->
|
||||
PassphraseInfoBottomSheetContent(content)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PassphraseInfoBottomSheetContent(content: ShowPassphraseInfoBottomSheetContent) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = TangemTheme.dimens.size40)
|
||||
.size(TangemTheme.dimens.size48),
|
||||
painter = painterResource(id = R.drawable.ic_information_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.common_passphrase),
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.size40)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.h2,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(id = R.string.onboarding_bottom_sheet_passphrase_description),
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.size16)
|
||||
.padding(horizontal = TangemTheme.dimens.size24)
|
||||
.align(Alignment.CenterHorizontally),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
style = TangemTheme.typography.body2,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.size16)
|
||||
.padding(top = TangemTheme.dimens.size40)
|
||||
.padding(bottom = TangemTheme.dimens.size32)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.common_ok),
|
||||
onClick = content.onOkClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PassphraseInfoBottomSheetContentPreview() {
|
||||
TangemThemePreview {
|
||||
PassphraseInfoBottomSheetContent(ShowPassphraseInfoBottomSheetContent { })
|
||||
}
|
||||
}
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
|
||||
import com.tangem.core.ui.extensions.pluralStringResourceSafe
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionSubTitleText
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionTitleText
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun YourSeedPhraseScreen(state: YourSeedPhraseState, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens.spacing92)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
SegmentSeedBlock(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing20),
|
||||
state = state.segmentSeedState,
|
||||
)
|
||||
|
||||
TitleBlock(state.segmentSeedState)
|
||||
|
||||
SeedPhraseGridBlock(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing20)
|
||||
.padding(horizontal = TangemTheme.dimens.size16),
|
||||
mnemonicGridItems = state.mnemonicGridItems,
|
||||
)
|
||||
}
|
||||
OnboardingActionBlock(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
firstActionContent = {
|
||||
PrimaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResourceSafe(id = R.string.common_continue),
|
||||
onClick = state.buttonContinue.onClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TitleBlock(state: SegmentSeedState) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.size36)
|
||||
.padding(top = TangemTheme.dimens.spacing20)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Column {
|
||||
DescriptionTitleText(text = stringResourceSafe(id = R.string.onboarding_seed_generate_title))
|
||||
SpacerH16()
|
||||
DescriptionSubTitleText(
|
||||
text = pluralStringResourceSafe(
|
||||
id = R.plurals.onboarding_seed_generate_message_words_count,
|
||||
count = state.selectedSeedType.count,
|
||||
state.selectedSeedType.count,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SegmentSeedBlock(state: SegmentSeedState, modifier: Modifier = Modifier) {
|
||||
SegmentedButtons(
|
||||
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing76),
|
||||
config = state.seedSegments,
|
||||
initialSelectedItem = state.selectedSeedType,
|
||||
onClick = {
|
||||
state.onSelectType(it)
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = pluralStringResourceSafe(
|
||||
id = R.plurals.onboarding_seed_generate_words_count,
|
||||
count = it.count,
|
||||
it.count,
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing10)
|
||||
.fillMaxWidth(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeedPhraseGridBlock(mnemonicGridItems: ImmutableList<MnemonicGridItem>, modifier: Modifier = Modifier) {
|
||||
VerticalGrid(
|
||||
modifier = modifier,
|
||||
items = mnemonicGridItems,
|
||||
columns = 2,
|
||||
) { item ->
|
||||
Row(
|
||||
modifier = Modifier.padding(all = TangemTheme.dimens.size8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.width(TangemTheme.dimens.size40),
|
||||
text = "${item.index}.",
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Text(
|
||||
text = item.mnemonic,
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private inline fun <T> VerticalGrid(
|
||||
items: ImmutableList<T>,
|
||||
columns: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
crossinline content: @Composable (T) -> Unit,
|
||||
) {
|
||||
val columnLength = items.size / columns
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
for (index in 0 until columns) {
|
||||
Column {
|
||||
for (i in 0 until columnLength) {
|
||||
val item = items[index * columnLength + i]
|
||||
content(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360, showBackground = true)
|
||||
@Preview(widthDp = 360, showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun YourSeedPhraseScreenPreview_Light() {
|
||||
TangemThemePreview {
|
||||
YourSeedPhraseScreen(
|
||||
state = YourSeedPhraseState(
|
||||
segmentSeedState = SegmentSeedState(
|
||||
seedSegments = persistentListOf(
|
||||
SegmentSeedType.SEED_12,
|
||||
SegmentSeedType.SEED_24,
|
||||
),
|
||||
selectedSeedType = SegmentSeedType.SEED_24,
|
||||
onSelectType = {},
|
||||
),
|
||||
mnemonicGridItems = (1..24).map { MnemonicGridItem(it, it.toString()) }.toPersistentList(),
|
||||
buttonContinue = ButtonState(
|
||||
enabled = true,
|
||||
isClickable = true,
|
||||
showProgress = false,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = TangemTheme.colors.background.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.SpacerH12
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Composable
|
||||
fun OnboardingActionBlock(
|
||||
modifier: Modifier = Modifier,
|
||||
firstActionContent: @Composable (() -> Unit)? = null,
|
||||
secondActionContent: @Composable (() -> Unit)? = null,
|
||||
) {
|
||||
if (firstActionContent == null && secondActionContent == null) return
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.padding(horizontal = TangemTheme.dimens.size16)
|
||||
.padding(
|
||||
top = TangemTheme.dimens.size8,
|
||||
bottom = TangemTheme.dimens.size32,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
) {
|
||||
firstActionContent?.invoke()
|
||||
if (firstActionContent != null && secondActionContent != null) SpacerH12()
|
||||
secondActionContent?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* UI component witch provides single and carousel description for the onboarding process
|
||||
*/
|
||||
@Composable
|
||||
fun OnboardingDescriptionBlock(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.size36),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Description(@StringRes titleRes: Int, @StringRes subTitleRes: Int) {
|
||||
Column {
|
||||
DescriptionTitleText(text = stringResourceSafe(id = titleRes))
|
||||
SpacerH16()
|
||||
DescriptionSubTitleText(text = stringResourceSafe(id = subTitleRes))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DescriptionTitleText(text: String) {
|
||||
Text(
|
||||
text = text,
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DescriptionSubTitleText(text: String) {
|
||||
// the text style made similar to TextViewOnboarding.Body
|
||||
Text(
|
||||
text = text,
|
||||
style = TangemTheme.typography.body1.copy(
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.03.sp,
|
||||
),
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders
|
||||
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CheckSeedPhraseStateBuilder {
|
||||
|
||||
fun updateTextField(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
field: SeedPhraseField,
|
||||
textFieldValue: TextFieldValue,
|
||||
): OnboardingSeedPhraseState = when (field) {
|
||||
SeedPhraseField.Second -> uiState.copy(
|
||||
checkSeedPhraseState = uiState.checkSeedPhraseState.copy(
|
||||
tvSecondPhrase = uiState.checkSeedPhraseState.tvSecondPhrase.copy(
|
||||
textFieldValue = textFieldValue,
|
||||
),
|
||||
),
|
||||
)
|
||||
SeedPhraseField.Seventh -> uiState.copy(
|
||||
checkSeedPhraseState = uiState.checkSeedPhraseState.copy(
|
||||
tvSeventhPhrase = uiState.checkSeedPhraseState.tvSeventhPhrase.copy(
|
||||
textFieldValue = textFieldValue,
|
||||
),
|
||||
),
|
||||
)
|
||||
SeedPhraseField.Eleventh -> uiState.copy(
|
||||
checkSeedPhraseState = uiState.checkSeedPhraseState.copy(
|
||||
tvEleventhPhrase = uiState.checkSeedPhraseState.tvEleventhPhrase.copy(
|
||||
textFieldValue = textFieldValue,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun updateTextFieldError(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
field: SeedPhraseField,
|
||||
hasError: Boolean,
|
||||
): OnboardingSeedPhraseState = when (field) {
|
||||
SeedPhraseField.Second -> uiState.copy(
|
||||
checkSeedPhraseState = uiState.checkSeedPhraseState.copy(
|
||||
tvSecondPhrase = uiState.checkSeedPhraseState.tvSecondPhrase.copy(
|
||||
isError = hasError,
|
||||
),
|
||||
),
|
||||
)
|
||||
SeedPhraseField.Seventh -> uiState.copy(
|
||||
checkSeedPhraseState = uiState.checkSeedPhraseState.copy(
|
||||
tvSeventhPhrase = uiState.checkSeedPhraseState.tvSeventhPhrase.copy(
|
||||
isError = hasError,
|
||||
),
|
||||
),
|
||||
)
|
||||
SeedPhraseField.Eleventh -> uiState.copy(
|
||||
checkSeedPhraseState = uiState.checkSeedPhraseState.copy(
|
||||
tvEleventhPhrase = uiState.checkSeedPhraseState.tvEleventhPhrase.copy(
|
||||
isError = hasError,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun updateCreateWalletButton(uiState: OnboardingSeedPhraseState, enabled: Boolean): OnboardingSeedPhraseState {
|
||||
return uiState.copy(
|
||||
checkSeedPhraseState = uiState.checkSeedPhraseState.copy(
|
||||
buttonCreateWallet = uiState.checkSeedPhraseState.buttonCreateWallet.copy(
|
||||
enabled = enabled,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders
|
||||
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.feature.onboarding.domain.InsertSuggestionResult
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseError
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.ShowPassphraseInfoBottomSheetContent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ImportSeedPhraseStateBuilder {
|
||||
|
||||
fun updateSeedPhraseTextField(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
textFieldValue: TextFieldValue,
|
||||
): OnboardingSeedPhraseState = uiState.copy(
|
||||
importSeedPhraseState = uiState.importSeedPhraseState.copy(
|
||||
fieldSeedPhrase = uiState.importSeedPhraseState.fieldSeedPhrase.copy(
|
||||
textFieldValue = textFieldValue,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fun updatePassPhraseTextField(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
textFieldValue: TextFieldValue,
|
||||
): OnboardingSeedPhraseState = uiState.copy(
|
||||
importSeedPhraseState = uiState.importSeedPhraseState.copy(
|
||||
fieldPassphrase = uiState.importSeedPhraseState.fieldPassphrase.copy(
|
||||
textFieldValue = textFieldValue,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fun updateCreateWalletButton(uiState: OnboardingSeedPhraseState, enabled: Boolean): OnboardingSeedPhraseState {
|
||||
return uiState.copy(
|
||||
importSeedPhraseState = uiState.importSeedPhraseState.copy(
|
||||
buttonCreateWallet = uiState.importSeedPhraseState.buttonCreateWallet.copy(
|
||||
enabled = enabled,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun updateInvalidWords(uiState: OnboardingSeedPhraseState, invalidWords: Set<String>): OnboardingSeedPhraseState =
|
||||
uiState.copy(
|
||||
importSeedPhraseState = uiState.importSeedPhraseState.copy(
|
||||
invalidWords = invalidWords,
|
||||
),
|
||||
)
|
||||
|
||||
fun updateSuggestions(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
suggestions: ImmutableList<String>,
|
||||
): OnboardingSeedPhraseState = uiState.copy(
|
||||
importSeedPhraseState = uiState.importSeedPhraseState.copy(
|
||||
suggestionsList = suggestions,
|
||||
),
|
||||
)
|
||||
|
||||
fun showPassphraseInfoBottomSheet(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
onDismiss: () -> Unit,
|
||||
): OnboardingSeedPhraseState {
|
||||
return uiState.copy(
|
||||
importSeedPhraseState = uiState.importSeedPhraseState.copy(
|
||||
bottomSheetConfig = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = onDismiss,
|
||||
content = ShowPassphraseInfoBottomSheetContent(onDismiss),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun dismissPassphraseBottomSheet(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState {
|
||||
return uiState.copy(
|
||||
importSeedPhraseState = uiState.importSeedPhraseState.copy(
|
||||
bottomSheetConfig = uiState.importSeedPhraseState.bottomSheetConfig?.copy(
|
||||
isShown = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun insertSuggestionWord(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
insertResult: InsertSuggestionResult,
|
||||
): OnboardingSeedPhraseState {
|
||||
val newTextFieldValue = uiState.importSeedPhraseState.fieldSeedPhrase.textFieldValue.copy(
|
||||
text = insertResult.text,
|
||||
selection = TextRange(insertResult.cursorPosition, insertResult.cursorPosition),
|
||||
)
|
||||
return updateSeedPhraseTextField(uiState, newTextFieldValue)
|
||||
}
|
||||
|
||||
fun updateError(uiState: OnboardingSeedPhraseState, error: SeedPhraseError?): OnboardingSeedPhraseState {
|
||||
return uiState.copy(
|
||||
importSeedPhraseState = uiState.importSeedPhraseState.copy(
|
||||
error = error,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders
|
||||
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.*
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class StateBuilder(
|
||||
private val uiActions: UiActions,
|
||||
) {
|
||||
|
||||
val checkSeedPhrase: CheckSeedPhraseStateBuilder = CheckSeedPhraseStateBuilder()
|
||||
val importSeedPhrase: ImportSeedPhraseStateBuilder = ImportSeedPhraseStateBuilder()
|
||||
|
||||
fun init(selectedSeedType: SegmentSeedType): OnboardingSeedPhraseState = OnboardingSeedPhraseState(
|
||||
introState = getInitialIntroState(),
|
||||
aboutState = getInitialAboutState(),
|
||||
yourSeedPhraseState = getInitialYourSeedPhraseState(selectedSeedType),
|
||||
checkSeedPhraseState = getInitialCheckSeedPhraseState(),
|
||||
importSeedPhraseState = getInitialImportSeedPhraseState(),
|
||||
menuButtonChat = ButtonState(
|
||||
onClick = uiActions.menuChatClick,
|
||||
),
|
||||
onBackClick = uiActions.menuNavigateBackClick,
|
||||
)
|
||||
|
||||
private fun getInitialImportSeedPhraseState(): ImportSeedPhraseState {
|
||||
return ImportSeedPhraseState(
|
||||
fieldSeedPhrase = TextFieldState(
|
||||
onTextFieldValueChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onTextFieldChanged,
|
||||
onFocusChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onFocusChanged,
|
||||
),
|
||||
fieldPassphrase = TextFieldState(
|
||||
onTextFieldValueChanged = uiActions.importSeedPhraseActions.passTextFieldAction.onTextFieldChanged,
|
||||
onFocusChanged = uiActions.importSeedPhraseActions.passTextFieldAction.onFocusChanged,
|
||||
),
|
||||
onSuggestedPhraseClick = uiActions.importSeedPhraseActions.suggestedPhraseClick,
|
||||
onPassphraseInfoClick = uiActions.importSeedPhraseActions.onPassphraseInfoClick,
|
||||
buttonCreateWallet = ButtonState(
|
||||
enabled = false,
|
||||
onClick = uiActions.importSeedPhraseActions.buttonCreateWalletClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getInitialCheckSeedPhraseState(): CheckSeedPhraseState {
|
||||
return CheckSeedPhraseState(
|
||||
tvSecondPhrase = TextFieldState(
|
||||
label = "2",
|
||||
isFocused = true,
|
||||
onTextFieldValueChanged = uiActions.checkSeedPhraseActions.secondTextFieldAction.onTextFieldChanged,
|
||||
onFocusChanged = uiActions.checkSeedPhraseActions.secondTextFieldAction.onFocusChanged,
|
||||
),
|
||||
tvSeventhPhrase = TextFieldState(
|
||||
label = "7",
|
||||
isFocused = false,
|
||||
onTextFieldValueChanged = uiActions.checkSeedPhraseActions.seventhTextFieldAction.onTextFieldChanged,
|
||||
onFocusChanged = uiActions.checkSeedPhraseActions.seventhTextFieldAction.onFocusChanged,
|
||||
),
|
||||
tvEleventhPhrase = TextFieldState(
|
||||
label = "11",
|
||||
isFocused = false,
|
||||
onTextFieldValueChanged = uiActions.checkSeedPhraseActions.eleventhTextFieldAction.onTextFieldChanged,
|
||||
onFocusChanged = uiActions.checkSeedPhraseActions.eleventhTextFieldAction.onFocusChanged,
|
||||
),
|
||||
buttonCreateWallet = ButtonState(
|
||||
enabled = false,
|
||||
onClick = uiActions.checkSeedPhraseActions.buttonCreateWalletClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getInitialYourSeedPhraseState(selectedSeedType: SegmentSeedType): YourSeedPhraseState {
|
||||
return YourSeedPhraseState(
|
||||
segmentSeedState = SegmentSeedState(
|
||||
seedSegments = persistentListOf(
|
||||
SegmentSeedType.SEED_12,
|
||||
SegmentSeedType.SEED_24,
|
||||
),
|
||||
selectedSeedType = selectedSeedType,
|
||||
onSelectType = uiActions.yourSeedPhraseActions.onSelectType,
|
||||
),
|
||||
buttonContinue = ButtonState(
|
||||
onClick = uiActions.yourSeedPhraseActions.buttonContinueClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getInitialAboutState(): AboutState {
|
||||
return AboutState(
|
||||
buttonReadMoreAboutSeedPhrase = ButtonState(
|
||||
onClick = uiActions.aboutActions.buttonReadMoreAboutSeedPhraseClick,
|
||||
),
|
||||
buttonGenerateSeedPhrase = ButtonState(
|
||||
onClick = uiActions.aboutActions.buttonGenerateSeedPhraseClick,
|
||||
),
|
||||
buttonImportSeedPhrase = ButtonState(
|
||||
onClick = uiActions.aboutActions.buttonImportSeedPhraseClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getInitialIntroState(): IntroState {
|
||||
return IntroState(
|
||||
buttonCreateWallet = ButtonState(
|
||||
onClick = uiActions.introActions.buttonCreateWalletClick,
|
||||
),
|
||||
buttonOtherOptions = ButtonState(
|
||||
onClick = uiActions.introActions.buttonOtherOptionsClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun setCardArtwork(uiState: OnboardingSeedPhraseState, cardArtwork: String): OnboardingSeedPhraseState =
|
||||
uiState.copy(
|
||||
introState = uiState.introState.copy(
|
||||
cardImageUrl = cardArtwork,
|
||||
),
|
||||
)
|
||||
|
||||
fun generateMnemonicComponents(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState {
|
||||
return uiState.copy(
|
||||
aboutState = uiState.aboutState.copy(
|
||||
buttonGenerateSeedPhrase = uiState.aboutState.buttonGenerateSeedPhrase.copy(
|
||||
showProgress = true,
|
||||
),
|
||||
buttonImportSeedPhrase = uiState.aboutState.buttonImportSeedPhrase.copy(
|
||||
enabled = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun mnemonicGenerated(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
mnemonicGridItems: PersistentList<MnemonicGridItem>,
|
||||
): OnboardingSeedPhraseState {
|
||||
return updateMnemonicComponents(uiState, mnemonicGridItems)
|
||||
.copy(
|
||||
aboutState = uiState.aboutState.copy(
|
||||
buttonGenerateSeedPhrase = uiState.aboutState.buttonGenerateSeedPhrase.copy(
|
||||
showProgress = false,
|
||||
),
|
||||
buttonImportSeedPhrase = uiState.aboutState.buttonImportSeedPhrase.copy(
|
||||
enabled = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun selectSeedType(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
mnemonicGridItems: PersistentList<MnemonicGridItem>,
|
||||
selectedSeedType: SegmentSeedType,
|
||||
): OnboardingSeedPhraseState = uiState.copy(
|
||||
yourSeedPhraseState = uiState.yourSeedPhraseState.copy(
|
||||
mnemonicGridItems = mnemonicGridItems,
|
||||
segmentSeedState = uiState.yourSeedPhraseState.segmentSeedState.copy(
|
||||
selectedSeedType = selectedSeedType,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
fun clearCheckSeedPhraseState(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState = uiState.copy(
|
||||
checkSeedPhraseState = uiState.checkSeedPhraseState.copy(
|
||||
tvSecondPhrase = TextFieldState(
|
||||
label = "2",
|
||||
isFocused = true,
|
||||
onTextFieldValueChanged = uiActions.checkSeedPhraseActions.secondTextFieldAction.onTextFieldChanged,
|
||||
onFocusChanged = uiActions.checkSeedPhraseActions.secondTextFieldAction.onFocusChanged,
|
||||
),
|
||||
tvSeventhPhrase = TextFieldState(
|
||||
label = "7",
|
||||
isFocused = false,
|
||||
onTextFieldValueChanged = uiActions.checkSeedPhraseActions.seventhTextFieldAction.onTextFieldChanged,
|
||||
onFocusChanged = uiActions.checkSeedPhraseActions.seventhTextFieldAction.onFocusChanged,
|
||||
),
|
||||
tvEleventhPhrase = TextFieldState(
|
||||
label = "11",
|
||||
isFocused = false,
|
||||
onTextFieldValueChanged = uiActions.checkSeedPhraseActions.eleventhTextFieldAction.onTextFieldChanged,
|
||||
onFocusChanged = uiActions.checkSeedPhraseActions.eleventhTextFieldAction.onFocusChanged,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private fun updateMnemonicComponents(
|
||||
uiState: OnboardingSeedPhraseState,
|
||||
mnemonicGridItems: PersistentList<MnemonicGridItem>,
|
||||
): OnboardingSeedPhraseState = uiState.copy(
|
||||
yourSeedPhraseState = uiState.yourSeedPhraseState.copy(
|
||||
mnemonicGridItems = mnemonicGridItems,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.viewmodel
|
||||
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.input.OffsetMapping
|
||||
import androidx.compose.ui.text.input.TransformedText
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import com.tangem.utils.extensions.isSingleItem
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class InvalidWordsColorTransformation(
|
||||
private val wordsToBrush: Set<String>,
|
||||
private val style: SpanStyle,
|
||||
) : VisualTransformation {
|
||||
|
||||
override fun filter(text: AnnotatedString): TransformedText {
|
||||
val plainText = text.text
|
||||
if (wordsToBrush.isSingleItem() && wordsToBrush.first() == plainText) {
|
||||
return plainText.annotate().toTransformedText()
|
||||
}
|
||||
|
||||
val wordList = makeSeparatedWordlist(plainText)
|
||||
val annotatedString = makeAnnotatedString(wordList)
|
||||
|
||||
return annotatedString.toTransformedText()
|
||||
}
|
||||
|
||||
private fun makeSeparatedWordlist(plainText: String): List<String> {
|
||||
val plainTextCharArray = plainText.toCharArray()
|
||||
val wordlist = mutableListOf<String>()
|
||||
var wordBuilder = StringBuilder()
|
||||
|
||||
for (index in plainTextCharArray.indices) {
|
||||
val char = plainTextCharArray[index]
|
||||
if (char.isWhitespace()) {
|
||||
if (wordBuilder.isEmpty()) {
|
||||
wordlist.add(WORD_SEPARATOR)
|
||||
} else {
|
||||
wordlist.add(wordBuilder.toString())
|
||||
wordBuilder = StringBuilder()
|
||||
wordlist.add(WORD_SEPARATOR)
|
||||
}
|
||||
} else {
|
||||
if (index == plainTextCharArray.lastIndex) {
|
||||
wordBuilder.append(char)
|
||||
wordlist.add(wordBuilder.toString())
|
||||
wordBuilder = StringBuilder()
|
||||
} else {
|
||||
wordBuilder.append(char)
|
||||
}
|
||||
}
|
||||
}
|
||||
return wordlist.toList()
|
||||
}
|
||||
|
||||
private fun makeAnnotatedString(wordList: List<String>): AnnotatedString = buildAnnotatedString {
|
||||
wordList.forEach {
|
||||
if (it == WORD_SEPARATOR) {
|
||||
append(WHITE_SPACE)
|
||||
} else if (wordsToBrush.contains(it)) {
|
||||
append(it.annotate())
|
||||
} else {
|
||||
append(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.annotate(): AnnotatedString = AnnotatedString(this, style)
|
||||
|
||||
private fun AnnotatedString.toTransformedText(): TransformedText = TransformedText(this, OffsetMapping.Identity)
|
||||
|
||||
companion object {
|
||||
private const val WHITE_SPACE = " "
|
||||
private const val WORD_SEPARATOR = "_$@-*"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.common.module.ModuleMessageConverter
|
||||
import com.tangem.feature.onboarding.R
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseError
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SeedPhraseErrorConverter : ModuleMessageConverter<Pair<Context, SeedPhraseError?>, String?> {
|
||||
|
||||
override fun convert(source: Pair<Context, SeedPhraseError?>): String? {
|
||||
val (context, message) = source
|
||||
|
||||
val convertedMessage = when (message) {
|
||||
SeedPhraseError.InvalidChecksum -> {
|
||||
context.getString(R.string.onboarding_seed_mnemonic_invalid_checksum)
|
||||
}
|
||||
is SeedPhraseError.InvalidWords -> {
|
||||
context.getString(R.string.onboarding_seed_mnemonic_wrong_words)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
return convertedMessage
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.viewmodel
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface SeedPhraseMediator {
|
||||
fun createWallet(callback: (CompletionResult<CreateWalletResponse>) -> Unit)
|
||||
fun importWallet(
|
||||
mnemonicComponents: List<String>,
|
||||
passphrase: String?,
|
||||
seedPhraseSource: SeedPhraseSource,
|
||||
callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
)
|
||||
|
||||
fun onWalletCreated(result: CompletionResult<CreateWalletResponse>)
|
||||
|
||||
fun allowScreenshots(allow: Boolean)
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SeedPhraseRouter(
|
||||
private val onBack: () -> Unit,
|
||||
private val onOpenChat: () -> Unit,
|
||||
private val onOpenUriClick: (Uri) -> Unit,
|
||||
) {
|
||||
|
||||
private val _currentScreen = MutableStateFlow(SeedPhraseScreen.Intro)
|
||||
|
||||
val currentScreen: StateFlow<SeedPhraseScreen>
|
||||
get() = _currentScreen
|
||||
|
||||
fun navigateBack() {
|
||||
_currentScreen.value = when (_currentScreen.value) {
|
||||
SeedPhraseScreen.CheckSeedPhrase -> SeedPhraseScreen.YourSeedPhrase
|
||||
SeedPhraseScreen.AboutSeedPhrase -> SeedPhraseScreen.Intro
|
||||
SeedPhraseScreen.ImportSeedPhrase,
|
||||
SeedPhraseScreen.YourSeedPhrase,
|
||||
-> SeedPhraseScreen.AboutSeedPhrase
|
||||
SeedPhraseScreen.Intro -> {
|
||||
onBack.invoke()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openScreen(screen: SeedPhraseScreen) {
|
||||
_currentScreen.value = screen
|
||||
}
|
||||
|
||||
fun openChat() {
|
||||
onOpenChat.invoke()
|
||||
}
|
||||
|
||||
fun openUri(uri: Uri) {
|
||||
onOpenUriClick.invoke(uri)
|
||||
}
|
||||
}
|
||||
|
||||
enum class SeedPhraseScreen {
|
||||
Intro, AboutSeedPhrase, YourSeedPhrase, CheckSeedPhrase, ImportSeedPhrase
|
||||
}
|
||||
|
|
@ -1,484 +0,0 @@
|
|||
package com.tangem.feature.onboarding.presentation.wallet2.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.common.locale.LocaleProvider
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseError
|
||||
import com.tangem.feature.onboarding.domain.SeedPhraseInteractor
|
||||
import com.tangem.feature.onboarding.domain.models.MnemonicType
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.CreateWalletEvents
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseEvents
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.*
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.model.SegmentSeedType.Companion.fromMnemonicType
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders.StateBuilder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.Debouncer
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LargeClass")
|
||||
@HiltViewModel
|
||||
class SeedPhraseViewModel @Inject constructor(
|
||||
private val interactor: SeedPhraseInteractor,
|
||||
private val localeProvider: LocaleProvider,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : ViewModel() {
|
||||
|
||||
private var uiBuilder = StateBuilder(uiActions = createUiActions())
|
||||
|
||||
private var selectedMnemonicType = MnemonicType.Mnemonic12
|
||||
var uiState: OnboardingSeedPhraseState by mutableStateOf(uiBuilder.init(fromMnemonicType(selectedMnemonicType)))
|
||||
private set
|
||||
|
||||
private lateinit var router: SeedPhraseRouter
|
||||
private lateinit var mediator: SeedPhraseMediator
|
||||
|
||||
val currentScreen: StateFlow<SeedPhraseScreen>
|
||||
get() = router.currentScreen
|
||||
|
||||
val progress: Flow<Int>
|
||||
get() = currentScreen.map { progressByScreen[it] ?: 0 }
|
||||
|
||||
val maxProgress: Int by lazy { progressByScreen.maxOfOrNull { it.value } ?: 0 }
|
||||
|
||||
var isFinished: Boolean = false
|
||||
private set
|
||||
|
||||
private val progressByScreen = mapOf(
|
||||
SeedPhraseScreen.Intro to 1,
|
||||
SeedPhraseScreen.AboutSeedPhrase to 2,
|
||||
SeedPhraseScreen.YourSeedPhrase to 3,
|
||||
SeedPhraseScreen.CheckSeedPhrase to 3,
|
||||
SeedPhraseScreen.ImportSeedPhrase to 3,
|
||||
)
|
||||
|
||||
private val textFieldsDebouncers = mutableMapOf<String, Debouncer>()
|
||||
|
||||
private var generatedMnemonicComponents: Map<MnemonicType, List<String>> = emptyMap()
|
||||
private var importedMnemonicComponents: List<String>? = null
|
||||
private var importedPassphrase: String? = null
|
||||
|
||||
override fun onCleared() {
|
||||
textFieldsDebouncers.forEach { entry -> entry.value.release() }
|
||||
textFieldsDebouncers.clear()
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
fun setRouter(router: SeedPhraseRouter) {
|
||||
this.router = router
|
||||
subscribeToScreenChanges()
|
||||
}
|
||||
|
||||
private fun subscribeToScreenChanges() {
|
||||
router.currentScreen.onEach { screen ->
|
||||
when (screen) {
|
||||
SeedPhraseScreen.Intro -> {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
SeedPhraseScreen.AboutSeedPhrase -> {
|
||||
analyticsEventHandler.send(SeedPhraseEvents.IntroScreenOpened)
|
||||
mediator.allowScreenshots(true)
|
||||
}
|
||||
|
||||
SeedPhraseScreen.YourSeedPhrase -> {
|
||||
analyticsEventHandler.send(SeedPhraseEvents.GenerationScreenOpened)
|
||||
mediator.allowScreenshots(false)
|
||||
}
|
||||
|
||||
SeedPhraseScreen.CheckSeedPhrase -> {
|
||||
analyticsEventHandler.send(SeedPhraseEvents.CheckingScreenOpened)
|
||||
mediator.allowScreenshots(true)
|
||||
}
|
||||
|
||||
SeedPhraseScreen.ImportSeedPhrase -> {
|
||||
analyticsEventHandler.send(SeedPhraseEvents.ImportScreenOpened)
|
||||
}
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun setMediator(mediator: SeedPhraseMediator) {
|
||||
this.mediator = mediator
|
||||
}
|
||||
|
||||
fun setCardArtworkUri(cardArtworkUri: String) {
|
||||
if (uiState.introState.cardImageUrl != cardArtworkUri) {
|
||||
uiState = uiBuilder.setCardArtwork(uiState, cardArtworkUri)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createUiActions(): UiActions = UiActions(
|
||||
introActions = IntroUiAction(
|
||||
buttonCreateWalletClick = ::buttonCreateWalletClick,
|
||||
buttonOtherOptionsClick = ::buttonOtherOptionsClick,
|
||||
),
|
||||
aboutActions = AboutUiAction(
|
||||
buttonReadMoreAboutSeedPhraseClick = ::buttonReadMoreAboutSeedPhraseClick,
|
||||
buttonGenerateSeedPhraseClick = ::buttonGenerateSeedPhraseClick,
|
||||
buttonImportSeedPhraseClick = ::buttonImportSeedPhraseClick,
|
||||
),
|
||||
yourSeedPhraseActions = YourSeedPhraseUiAction(
|
||||
buttonContinueClick = ::buttonContinueClick,
|
||||
onSelectType = ::onSelectType,
|
||||
),
|
||||
checkSeedPhraseActions = CheckSeedPhraseUiAction(
|
||||
buttonCreateWalletClick = {
|
||||
val mnemonicComponents =
|
||||
generatedMnemonicComponents[selectedMnemonicType] ?: return@CheckSeedPhraseUiAction
|
||||
buttonImportWalletClick(mnemonicComponents, null, SeedPhraseSource.GENERATED)
|
||||
},
|
||||
secondTextFieldAction = TextFieldUiAction(
|
||||
onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Second, value) },
|
||||
),
|
||||
seventhTextFieldAction = TextFieldUiAction(
|
||||
onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Seventh, value) },
|
||||
),
|
||||
eleventhTextFieldAction = TextFieldUiAction(
|
||||
onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Eleventh, value) },
|
||||
),
|
||||
),
|
||||
importSeedPhraseActions = ImportSeedPhraseUiAction(
|
||||
phraseTextFieldAction = TextFieldUiAction(
|
||||
onTextFieldChanged = { value -> onSeedPhraseTextFieldChanged(value) },
|
||||
),
|
||||
passTextFieldAction = TextFieldUiAction(
|
||||
onTextFieldChanged = { value -> onPassPhraseTextFieldChanged(value) },
|
||||
),
|
||||
suggestedPhraseClick = ::buttonSuggestedPhraseClick,
|
||||
onPassphraseInfoClick = ::buttonPassphraseInfoClick,
|
||||
buttonCreateWalletClick = {
|
||||
analyticsEventHandler.send(SeedPhraseEvents.ButtonImport)
|
||||
buttonImportWalletClick(importedMnemonicComponents, importedPassphrase, SeedPhraseSource.IMPORTED)
|
||||
},
|
||||
),
|
||||
menuChatClick = ::menuChatClick,
|
||||
menuNavigateBackClick = ::menuNavigateBackClick,
|
||||
)
|
||||
|
||||
// region CheckSeedPhrase
|
||||
private fun onTextFieldChanged(field: SeedPhraseField, textFieldValue: TextFieldValue) {
|
||||
launchSingle {
|
||||
updateUi { uiBuilder.checkSeedPhrase.updateTextField(uiState, field, textFieldValue) }
|
||||
|
||||
val fieldState = field.getState(uiState)
|
||||
if (fieldState.textFieldValue.text.isEmpty()) {
|
||||
updateUi {
|
||||
val mediate = uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError = false)
|
||||
uiBuilder.checkSeedPhrase.updateCreateWalletButton(mediate, enabled = false)
|
||||
}
|
||||
return@launchSingle
|
||||
}
|
||||
|
||||
createOrGetDebouncer(field.name).debounce(viewModelScope, context = dispatchers.io) {
|
||||
val mnemonicComponents = generatedMnemonicComponents[selectedMnemonicType] ?: return@debounce
|
||||
val hasError = !interactor.isWordMatch(mnemonicComponents, field, textFieldValue.text)
|
||||
if (fieldState.isError != hasError) {
|
||||
updateUi { uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError) }
|
||||
}
|
||||
val isCreateWalletButtonEnabled = SeedPhraseField.entries
|
||||
.map { field -> field.getState(uiState) }
|
||||
.all { fieldState -> fieldState.textFieldValue.text.isNotEmpty() && !fieldState.isError }
|
||||
|
||||
if (uiState.checkSeedPhraseState.buttonCreateWallet.enabled != isCreateWalletButtonEnabled) {
|
||||
updateUi {
|
||||
uiBuilder.checkSeedPhrase.updateCreateWalletButton(
|
||||
uiState = uiState,
|
||||
enabled = isCreateWalletButtonEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion CheckSeedPhrase
|
||||
|
||||
// region ImportSeedPhrase
|
||||
private fun onSeedPhraseTextFieldChanged(textFieldValue: TextFieldValue) {
|
||||
val oldTextFieldValue = uiState.importSeedPhraseState.fieldSeedPhrase.textFieldValue
|
||||
val isSameText = textFieldValue.text == oldTextFieldValue.text
|
||||
val isCursorMoved = textFieldValue.selection != oldTextFieldValue.selection
|
||||
|
||||
uiState = uiBuilder.importSeedPhrase.updateSeedPhraseTextField(uiState, textFieldValue)
|
||||
|
||||
val fieldState = uiState.importSeedPhraseState.fieldSeedPhrase
|
||||
val inputMnemonic = fieldState.textFieldValue.text
|
||||
|
||||
val debouncer = createOrGetDebouncer(MNEMONIC_DEBOUNCER)
|
||||
when {
|
||||
isSameText && isCursorMoved -> {
|
||||
debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.single) {
|
||||
updateSuggestions(fieldState)
|
||||
}
|
||||
}
|
||||
isSameText && !isCursorMoved -> {
|
||||
// do nothing
|
||||
}
|
||||
else -> {
|
||||
debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.single) {
|
||||
updateSuggestions(fieldState)
|
||||
validateMnemonic(inputMnemonic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onPassPhraseTextFieldChanged(textFieldValue: TextFieldValue) {
|
||||
importedPassphrase = textFieldValue.text
|
||||
uiState = uiBuilder.importSeedPhrase.updatePassPhraseTextField(uiState, textFieldValue)
|
||||
}
|
||||
|
||||
private suspend fun validateMnemonic(inputMnemonic: String) {
|
||||
if (inputMnemonic.isEmpty() && uiState.importSeedPhraseState.error != null) {
|
||||
val mediateState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false)
|
||||
updateUi {
|
||||
uiBuilder.importSeedPhrase.updateError(mediateState, null)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
interactor.validateMnemonicString(inputMnemonic)
|
||||
.onSuccess { mnemonicComponents ->
|
||||
importedMnemonicComponents = mnemonicComponents
|
||||
val mediateState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = true)
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, null) }
|
||||
}
|
||||
.onFailure {
|
||||
uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(uiState, enabled = false)
|
||||
val error = it as? SeedPhraseError ?: return
|
||||
|
||||
importedMnemonicComponents = null
|
||||
val mediateState = when (error) {
|
||||
is SeedPhraseError.InvalidWords -> {
|
||||
uiBuilder.importSeedPhrase.updateInvalidWords(uiState, error.words)
|
||||
}
|
||||
else -> uiState
|
||||
}
|
||||
updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, error) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateSuggestions(fieldState: TextFieldState) {
|
||||
val suggestions = interactor.getSuggestions(
|
||||
text = fieldState.textFieldValue.text,
|
||||
hasSelection = !fieldState.textFieldValue.selection.collapsed,
|
||||
cursorPosition = fieldState.textFieldValue.selection.end,
|
||||
)
|
||||
updateUi { uiBuilder.importSeedPhrase.updateSuggestions(uiState, suggestions) }
|
||||
}
|
||||
// endregion ImportSeedPhrase
|
||||
|
||||
// region ButtonClickHandlers
|
||||
private fun buttonCreateWalletClick() {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
analyticsEventHandler.send(CreateWalletEvents.OnboardingSeedButtonCreateWallet)
|
||||
mediator.createWallet(::handleWalletCreationResult)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buttonImportWalletClick(
|
||||
mnemonicComponents: List<String>?,
|
||||
passphrase: String?,
|
||||
seedPhraseSource: SeedPhraseSource,
|
||||
) {
|
||||
mnemonicComponents ?: return
|
||||
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
mediator.importWallet(mnemonicComponents, passphrase, seedPhraseSource, ::handleWalletCreationResult)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleWalletCreationResult(result: CompletionResult<CreateWalletResponse>) {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
isFinished = true
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
// errors shows on the TangemSdk bottom sheet dialog
|
||||
}
|
||||
}
|
||||
mediator.onWalletCreated(result)
|
||||
}
|
||||
|
||||
private fun menuNavigateBackClick() {
|
||||
router.navigateBack()
|
||||
}
|
||||
|
||||
private fun menuChatClick() {
|
||||
router.openChat()
|
||||
}
|
||||
|
||||
private fun buttonOtherOptionsClick() {
|
||||
analyticsEventHandler.send(CreateWalletEvents.OnboardingSeedButtonOtherOptions)
|
||||
router.openScreen(SeedPhraseScreen.AboutSeedPhrase)
|
||||
}
|
||||
|
||||
private fun buttonReadMoreAboutSeedPhraseClick() {
|
||||
analyticsEventHandler.send(SeedPhraseEvents.ButtonReadMore)
|
||||
val webUri = Uri.Builder()
|
||||
.scheme("https")
|
||||
.authority("tangem.com")
|
||||
.appendPath(localeProvider.getWebUriLocaleLanguage())
|
||||
.appendPath("blog/post/seed-phrase-a-risky-solution")
|
||||
.build()
|
||||
|
||||
router.openUri(webUri)
|
||||
}
|
||||
|
||||
private fun buttonGenerateSeedPhraseClick() {
|
||||
val mnemonicTypes = listOf(MnemonicType.Mnemonic12, MnemonicType.Mnemonic24)
|
||||
analyticsEventHandler.send(SeedPhraseEvents.ButtonGenerateSeedPhrase)
|
||||
launchSingle {
|
||||
updateUi { uiBuilder.generateMnemonicComponents(uiState) }
|
||||
delay(DELAY_GENERATE_SEED_PHRASE)
|
||||
interactor.generateMnemonics(mnemonicTypes)
|
||||
.onSuccess { mnemonics ->
|
||||
generatedMnemonicComponents = mnemonics.mapValues { it.value.mnemonicComponents }
|
||||
val defaultMnemonic = mnemonics[selectedMnemonicType]?.mnemonicComponents ?: return@launchSingle
|
||||
val mnemonicGridItems = toMnemonicGridList(defaultMnemonic)
|
||||
updateUi {
|
||||
router.openScreen(SeedPhraseScreen.YourSeedPhrase)
|
||||
uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toPersistentList())
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
generatedMnemonicComponents = emptyMap()
|
||||
// TODO: show error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toMnemonicGridList(mnemonicComponents: List<String>): PersistentList<MnemonicGridItem> {
|
||||
return mnemonicComponents.mapIndexed { index, word ->
|
||||
MnemonicGridItem(
|
||||
index = index.inc(),
|
||||
mnemonic = word,
|
||||
)
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
||||
private fun buttonImportSeedPhraseClick() {
|
||||
analyticsEventHandler.send(SeedPhraseEvents.ButtonImportWallet)
|
||||
router.openScreen(SeedPhraseScreen.ImportSeedPhrase)
|
||||
}
|
||||
|
||||
private fun buttonContinueClick() {
|
||||
launchSingle {
|
||||
updateUi {
|
||||
uiBuilder.clearCheckSeedPhraseState(uiState)
|
||||
}
|
||||
router.openScreen(SeedPhraseScreen.CheckSeedPhrase)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSelectType(selectedSeedType: SegmentSeedType) {
|
||||
launchSingle {
|
||||
selectedMnemonicType = selectedSeedType.toMnemonicType()
|
||||
val selectedMnemonic = generatedMnemonicComponents[selectedMnemonicType] ?: return@launchSingle
|
||||
updateUi {
|
||||
uiBuilder.selectSeedType(
|
||||
uiState = uiState,
|
||||
mnemonicGridItems = toMnemonicGridList(selectedMnemonic),
|
||||
selectedSeedType = selectedSeedType,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buttonSuggestedPhraseClick(suggestionIndex: Int) {
|
||||
launchSingle {
|
||||
val textFieldValue = uiState.importSeedPhraseState.fieldSeedPhrase.textFieldValue
|
||||
val word = uiState.importSeedPhraseState.suggestionsList[suggestionIndex]
|
||||
val cursorPosition = textFieldValue.selection.end
|
||||
|
||||
val insertResult = interactor.insertSuggestionWord(
|
||||
text = textFieldValue.text,
|
||||
suggestion = word,
|
||||
cursorPosition = cursorPosition,
|
||||
)
|
||||
updateUi {
|
||||
val mediateState = uiBuilder.importSeedPhrase.insertSuggestionWord(uiState, insertResult)
|
||||
uiBuilder.importSeedPhrase.updateSuggestions(mediateState, persistentListOf())
|
||||
// copy of TextFieldValue has no effect for textChange callback and seed validation doesn't work
|
||||
// call validation force
|
||||
}
|
||||
validateMnemonic(uiState.importSeedPhraseState.fieldSeedPhrase.textFieldValue.text)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buttonPassphraseInfoClick() {
|
||||
launchSingle {
|
||||
updateUi {
|
||||
uiBuilder.importSeedPhrase.showPassphraseInfoBottomSheet(uiState) {
|
||||
dismissPassphraseBottomSheet()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissPassphraseBottomSheet() {
|
||||
launchSingle {
|
||||
updateUi { uiBuilder.importSeedPhrase.dismissPassphraseBottomSheet(uiState) }
|
||||
}
|
||||
}
|
||||
// endregion ButtonClickHandlers
|
||||
|
||||
// region Utils
|
||||
/**
|
||||
* Updating the UI with a contract where all copying of objects are called in the Single thread context and
|
||||
* updating the UiState in the main context.
|
||||
*/
|
||||
private suspend fun updateUi(updateBlock: suspend () -> OnboardingSeedPhraseState) {
|
||||
withSingleContext {
|
||||
val newState = updateBlock.invoke()
|
||||
withMainContext { uiState = newState }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createOrGetDebouncer(name: String): Debouncer {
|
||||
return textFieldsDebouncers[name] ?: Debouncer().apply { textFieldsDebouncers[name] = this }
|
||||
}
|
||||
|
||||
private fun SeedPhraseField.getState(uiState: OnboardingSeedPhraseState): TextFieldState = when (this) {
|
||||
SeedPhraseField.Second -> uiState.checkSeedPhraseState.tvSecondPhrase
|
||||
SeedPhraseField.Seventh -> uiState.checkSeedPhraseState.tvSeventhPhrase
|
||||
SeedPhraseField.Eleventh -> uiState.checkSeedPhraseState.tvEleventhPhrase
|
||||
}
|
||||
|
||||
private fun launchSingle(block: suspend CoroutineScope.() -> Unit): Job {
|
||||
return viewModelScope.launch(dispatchers.single, block = block)
|
||||
}
|
||||
|
||||
private suspend fun <T> withMainContext(block: suspend CoroutineScope.() -> T): T {
|
||||
return withContext(dispatchers.main, block)
|
||||
}
|
||||
|
||||
private suspend fun <T> withSingleContext(block: suspend CoroutineScope.() -> T): T {
|
||||
return withContext(dispatchers.single, block)
|
||||
}
|
||||
// endregion Utils
|
||||
|
||||
private companion object {
|
||||
const val MNEMONIC_DEBOUNCER = "MnemonicDebouncer"
|
||||
const val MNEMONIC_DEBOUNCE_DELAY = 700L
|
||||
const val DELAY_GENERATE_SEED_PHRASE = 300L
|
||||
}
|
||||
}
|
||||
|
|
@ -19,8 +19,6 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.LegacyAction
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
|
||||
|
|
@ -34,7 +32,6 @@ import com.tangem.feature.walletsettings.entity.WalletSettingsUM
|
|||
import com.tangem.feature.walletsettings.impl.R
|
||||
import com.tangem.feature.walletsettings.utils.ItemsBuilder
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -55,12 +52,10 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val analyticsContextProxy: AnalyticsContextProxy,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val getShouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
|
||||
private val isDemoCardUseCase: IsDemoCardUseCase,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val nftFeatureToggles: NFTFeatureToggles,
|
||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
val params: WalletSettingsComponent.Params = paramsContainer.require()
|
||||
|
|
@ -163,23 +158,12 @@ internal class WalletSettingsModel @Inject constructor(
|
|||
analyticsEventHandler.send(Settings.ButtonCreateBackup)
|
||||
analyticsContextProxy.addContext(scanResponse)
|
||||
|
||||
if (onboardingV2FeatureToggles.isOnboardingV2Enabled) {
|
||||
router.push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.AddBackupWallet1,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
reduxStateHolder.dispatch(
|
||||
LegacyAction.StartOnboardingProcess(
|
||||
scanResponse = scanResponse,
|
||||
canSkipBackup = false,
|
||||
),
|
||||
)
|
||||
|
||||
router.push(AppRoute.OnboardingWallet())
|
||||
}
|
||||
router.push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.AddBackupWallet1,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onCheckedNFTChange(isChecked: Boolean) {
|
||||
|
|
|
|||
|
|
@ -96,9 +96,6 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
//TODO: Create api/impl modules for onboarding [REDACTED_JIRA]
|
||||
implementation(projects.features.onboarding)
|
||||
|
||||
/** Feature Apis */
|
||||
implementation(projects.features.details.api)
|
||||
implementation(projects.features.manageTokens.api)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue