Updated on 2026-08-14
This commit is contained in:
parent
cf05b371ef
commit
e080401183
103 changed files with 142 additions and 10555 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue