Updated on 2026-08-14
This commit is contained in:
parent
ff1ac5d0e7
commit
283bc49d3a
18 changed files with 247 additions and 314 deletions
|
|
@ -30,6 +30,7 @@ import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
|||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
|
|
@ -170,6 +171,9 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
@Inject
|
||||
lateinit var saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var cardRepository: CardRepository
|
||||
// endregion Injected
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -256,6 +260,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
|
|||
generalUserWalletsListManager = generalUserWalletsListManager,
|
||||
wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase,
|
||||
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
|
||||
cardRepository = cardRepository,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.tap.common.extensions.inject
|
|||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -22,8 +21,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
|
|||
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage
|
||||
val onboardingManager = OnboardingManager(action.scanResponse, usedCardsPrefStorage)
|
||||
val onboardingManager = OnboardingManager(action.scanResponse)
|
||||
globalState.copy(onboardingState = OnboardingState(true, onboardingManager))
|
||||
}
|
||||
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
|
||||
|
|
|
|||
|
|
@ -31,9 +31,12 @@ import com.tangem.operations.backup.StartPrimaryCardLinkingTask
|
|||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
import com.tangem.operations.files.ReadFilesTask
|
||||
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.collections.set
|
||||
|
||||
|
|
@ -185,23 +188,28 @@ private class ScanWalletProcessor(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
) {
|
||||
val activationInProgress = preferencesStorage.usedCardsPrefStorage.isActivationInProgress(card.cardId)
|
||||
mainScope.launch {
|
||||
val activationInProgress = store.inject(DaggerGraphState::cardRepository)
|
||||
.isActivationInProgress(card.cardId)
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() && activationInProgress) {
|
||||
StartPrimaryCardLinkingTask().run(session) { linkingResult ->
|
||||
when (linkingResult) {
|
||||
is CompletionResult.Success -> {
|
||||
primaryCard = linkingResult.data
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
@Suppress("ComplexCondition")
|
||||
if (card.backupStatus == CardDTO.BackupStatus.NoBackup && card.wallets.isNotEmpty() &&
|
||||
activationInProgress
|
||||
) {
|
||||
StartPrimaryCardLinkingTask().run(session) { linkingResult ->
|
||||
when (linkingResult) {
|
||||
is CompletionResult.Success -> {
|
||||
primaryCard = linkingResult.data
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
}
|
||||
} else {
|
||||
deriveKeysIfNeeded(card, session, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,26 +29,26 @@ import timber.log.Timber
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object OnboardingHelper {
|
||||
fun isOnboardingCase(response: ScanResponse): Boolean {
|
||||
val cardInfoStorage = preferencesStorage.usedCardsPrefStorage
|
||||
suspend fun isOnboardingCase(response: ScanResponse): Boolean {
|
||||
val onboardingManager = store.state.globalState.onboardingState.onboardingManager
|
||||
val cardId = response.card.cardId
|
||||
return when {
|
||||
response.cardTypesResolver.isTangemTwins() -> {
|
||||
if (!response.twinsIsTwinned()) {
|
||||
true
|
||||
} else {
|
||||
cardInfoStorage.isActivationInProgress(cardId)
|
||||
onboardingManager?.isActivationInProgress(cardId) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> {
|
||||
val emptyWallets = response.card.wallets.isEmpty()
|
||||
val activationInProgress = cardInfoStorage.isActivationInProgress(cardId)
|
||||
val activationInProgress = onboardingManager?.isActivationInProgress(cardId)
|
||||
val backupNotActive = response.card.backupStatus?.isActive != true
|
||||
emptyWallets || activationInProgress || backupNotActive
|
||||
emptyWallets || activationInProgress == true || backupNotActive
|
||||
}
|
||||
|
||||
response.card.wallets.isNotEmpty() -> cardInfoStorage.isActivationInProgress(cardId)
|
||||
response.card.wallets.isNotEmpty() -> onboardingManager?.isActivationInProgress(cardId) ?: false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ 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.data.source.preferences.storage.UsedCardsPrefStorage
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.CardVerifyAndGetInfo
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.isPositive
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.domain.TapError
|
||||
|
|
@ -17,17 +18,17 @@ 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 timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingManager(
|
||||
var scanResponse: ScanResponse,
|
||||
private val usedCardsPrefStorage: UsedCardsPrefStorage,
|
||||
) {
|
||||
class OnboardingManager(var scanResponse: ScanResponse) {
|
||||
|
||||
private val cardRepository: CardRepository = store.inject(DaggerGraphState::cardRepository)
|
||||
private var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
|
||||
|
||||
suspend fun loadArtworkUrl(): String {
|
||||
|
|
@ -76,17 +77,23 @@ class OnboardingManager(
|
|||
)
|
||||
}
|
||||
|
||||
fun activationStarted(cardId: String) {
|
||||
usedCardsPrefStorage.activationStarted(cardId)
|
||||
suspend fun startActivation(cardId: String) {
|
||||
cardRepository.startCardActivation(cardId)
|
||||
}
|
||||
|
||||
fun activationFinished(cardId: String) {
|
||||
usedCardsPrefStorage.activationFinished(cardId)
|
||||
suspend fun finishActivation(cardId: String) {
|
||||
cardRepository.finishCardActivation(cardId)
|
||||
}
|
||||
|
||||
fun isActivationStarted(cardId: String): Boolean {
|
||||
return usedCardsPrefStorage.isActivationStarted(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(
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.tap.features.demo.DemoHelper
|
|||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
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
|
||||
|
|
@ -57,8 +58,10 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
|
||||
when (action) {
|
||||
is OnboardingNoteAction.Init -> {
|
||||
if (!onboardingManager.isActivationStarted(card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
scope.launch {
|
||||
if (!onboardingManager.isActivationStarted(card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.LoadCardArtwork -> {
|
||||
|
|
@ -86,8 +89,10 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
}
|
||||
OnboardingNoteStep.Done -> {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
onboardingManager.activationFinished(card.cardId)
|
||||
postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) }
|
||||
mainScope.launch {
|
||||
onboardingManager.finishActivation(card.cardId)
|
||||
postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) }
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
|
@ -101,7 +106,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
val updatedResponse = scanResponse.copy(card = result.data.card)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.activationStarted(updatedResponse.card.cardId)
|
||||
onboardingManager.startActivation(updatedResponse.card.cardId)
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet))
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ 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
|
||||
|
|
@ -43,8 +44,10 @@ private fun handleOtherCardsAction(action: Action) {
|
|||
|
||||
when (action) {
|
||||
is OnboardingOtherCardsAction.Init -> {
|
||||
if (!onboardingManager.isActivationStarted(card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
scope.launch {
|
||||
if (!onboardingManager.isActivationStarted(card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.LoadCardArtwork -> {
|
||||
|
|
@ -67,8 +70,10 @@ private fun handleOtherCardsAction(action: Action) {
|
|||
}
|
||||
OnboardingOtherCardsStep.Done -> {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
onboardingManager.activationFinished(card.cardId)
|
||||
postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) }
|
||||
mainScope.launch {
|
||||
onboardingManager.finishActivation(card.cardId)
|
||||
postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) }
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
|
|
@ -88,7 +93,7 @@ private fun handleOtherCardsAction(action: Action) {
|
|||
)
|
||||
val updatedCard = updatedResponse.card
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.activationStarted(updatedCard.cardId)
|
||||
onboardingManager.startActivation(updatedCard.cardId)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done))
|
||||
|
|
|
|||
|
|
@ -75,51 +75,61 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
|
||||
fun startCardActivation(cardId: String) {
|
||||
if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
|
||||
onboardingManager?.activationStarted(cardId)
|
||||
scope.launch {
|
||||
onboardingManager?.startActivation(cardId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun finishCardActivation() {
|
||||
if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
onboardingManager?.activationFinished(getScanResponse().card.cardId)
|
||||
twinCardsState.pairCardId?.let { onboardingManager?.activationFinished(it) }
|
||||
|
||||
val cardIds = listOfNotNull(getScanResponse().card.cardId, twinCardsState.pairCardId)
|
||||
|
||||
if (cardIds.isNotEmpty()) {
|
||||
mainScope.launch {
|
||||
onboardingManager?.finishActivation(cardIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (action) {
|
||||
is TwinCardsAction.Init -> {
|
||||
if (twinCardsState.currentStep is TwinCardsStep.WelcomeOnly) return
|
||||
mainScope.launch {
|
||||
if (twinCardsState.currentStep is TwinCardsStep.WelcomeOnly) return@launch
|
||||
|
||||
val scanResponse = getScanResponse()
|
||||
onboardingManager?.apply {
|
||||
if (!isActivationStarted(scanResponse.card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
|
||||
when (twinCardsState.mode) {
|
||||
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)
|
||||
val scanResponse = getScanResponse()
|
||||
onboardingManager?.apply {
|
||||
if (!isActivationStarted(scanResponse.card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning))
|
||||
|
||||
when (twinCardsState.mode) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.tangem.tap.common.analytics.events.AnalyticsParam
|
|||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||
|
|
@ -72,8 +73,10 @@ private fun handleWalletAction(action: Action) {
|
|||
when (action) {
|
||||
OnboardingWalletAction.Init -> {
|
||||
ifNotNull(onboardingManager, card) { manager, notNullCard ->
|
||||
if (!manager.isActivationStarted(notNullCard.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
mainScope.launch {
|
||||
if (!manager.isActivationStarted(notNullCard.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
when {
|
||||
|
|
@ -144,8 +147,10 @@ private fun handleWalletAction(action: Action) {
|
|||
onboardingManager.scanResponse = updatedResponse
|
||||
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
startCardActivation(updatedResponse)
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
mainScope.launch {
|
||||
onboardingManager.startActivation(updatedResponse.card.cardId)
|
||||
store.dispatchWithMain(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
|
|
@ -493,11 +498,13 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.DiscardSavedBackup -> {
|
||||
backupService.primaryCardId?.let {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
finishCardsActivationForDiscardedUnfinishedBackup(it)
|
||||
mainScope.launch {
|
||||
backupService.primaryCardId?.let {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(it)
|
||||
}
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.CheckForUnfinishedBackup -> {
|
||||
if (backupService.hasIncompletedBackup) {
|
||||
|
|
@ -514,17 +521,21 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
}
|
||||
is BackupAction.SkipBackup -> {
|
||||
Analytics.send(Onboarding.Backup.Skipped())
|
||||
|
||||
Analytics.send(Onboarding.Finished())
|
||||
finishCardActivation(gatherCardIds(backupState, card))
|
||||
|
||||
scope.launch {
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(
|
||||
cardIds = gatherCardIds(backupState, card),
|
||||
)
|
||||
}
|
||||
}
|
||||
is BackupAction.FinishBackup -> {
|
||||
if (action.withAnalytics) {
|
||||
Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber))
|
||||
}
|
||||
scope.launch {
|
||||
if (action.withAnalytics) {
|
||||
Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber))
|
||||
}
|
||||
|
||||
if (scanResponse != null) {
|
||||
scope.launch {
|
||||
if (scanResponse != null) {
|
||||
val userWallet = UserWalletBuilder(scanResponse)
|
||||
.backupCardsIds(backupState.backupCardIds.toSet())
|
||||
.build()
|
||||
|
|
@ -546,16 +557,22 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
)
|
||||
store.dispatchOnMain(GlobalAction.UpdateUserWalletsListManager(userWalletsListManager))
|
||||
}
|
||||
|
||||
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()) return@launch
|
||||
|
||||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds)
|
||||
}
|
||||
|
||||
val notActivatedCardIds = gatherCardIds(backupState, card)
|
||||
.mapNotNull { if (cardActivationIsFinished(it)) null else it }
|
||||
|
||||
// All cardIds may already be activated if the backup was skipped before.
|
||||
if (notActivatedCardIds.isEmpty()) return
|
||||
|
||||
Analytics.send(Onboarding.Finished())
|
||||
finishCardActivation(notActivatedCardIds)
|
||||
}
|
||||
|
||||
is BackupAction.ResetBackupCard -> {
|
||||
|
|
@ -566,36 +583,12 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
}
|
||||
}
|
||||
|
||||
private fun cardActivationIsFinished(cardId: String): Boolean {
|
||||
return preferencesStorage.usedCardsPrefStorage.isActivationFinished(cardId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Wallet cards start activation at OnboardingWalletAction.CreateWallet
|
||||
*/
|
||||
private fun startCardActivation(scanResponse: ScanResponse) {
|
||||
preferencesStorage.usedCardsPrefStorage.activationStarted(scanResponse.card.cardId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Wallet cards finish activation at BackupAction.SkipBackup and BackupAction.FinishBackup
|
||||
*/
|
||||
internal fun finishCardActivation(cardIds: List<String>) {
|
||||
cardIds.forEach { cardId ->
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun gatherCardIds(backupState: BackupState, card: CardDTO?): List<String> {
|
||||
return (listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
|
||||
.filterNotNull()
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun finishCardsActivationForDiscardedUnfinishedBackup(cardId: String) {
|
||||
preferencesStorage.usedCardsPrefStorage.activationFinished(cardId)
|
||||
}
|
||||
|
||||
private fun handleOnBackPressed(state: OnboardingWalletState) {
|
||||
when (state.backupState.backupStep) {
|
||||
BackupStep.InitBackup, BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
|||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
|
||||
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
|
||||
|
|
@ -63,6 +64,7 @@ data class DaggerGraphState(
|
|||
val generalUserWalletsListManager: UserWalletsListManager? = null,
|
||||
val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null,
|
||||
val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null,
|
||||
val cardRepository: CardRepository? = null,
|
||||
) : StateType {
|
||||
|
||||
inline fun <reified T> get(getDependency: DaggerGraphState.() -> T?): T {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ internal object AppPreferencesStoreModule {
|
|||
fun provideAppPreferencesStore(
|
||||
@ApplicationContext appContext: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@SdkMoshi moshi: Moshi,
|
||||
): AppPreferencesStore {
|
||||
return AppPreferencesStore(
|
||||
preferencesDataStore = PreferencesDataStore.getInstance(context = appContext, dispatcher = dispatchers.io),
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class AppPreferencesStore(
|
|||
* @see getObjectList
|
||||
* */
|
||||
inline fun <reified T> MutablePreferences.getObject(key: Preferences.Key<String>): T? {
|
||||
val adapter = moshi.adapter(T::class.java) // TODO: Support parameterized types
|
||||
val adapter = moshi.adapter(T::class.java)
|
||||
return this[key]?.let(adapter::fromJson)
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +55,15 @@ class AppPreferencesStore(
|
|||
return this[key]?.let(adapter::fromJson)
|
||||
}
|
||||
|
||||
/** Get list of data [T] by string [key] or default */
|
||||
inline fun <reified T> MutablePreferences.getObjectListOrDefault(
|
||||
key: Preferences.Key<String>,
|
||||
default: List<T>,
|
||||
): List<T> {
|
||||
val adapter = moshi.adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java))
|
||||
return this[key]?.let(adapter::fromJson) ?: default
|
||||
}
|
||||
|
||||
/** Get map with [String] key and value [V] by string [key] from [MutablePreferences] */
|
||||
inline fun <reified V> MutablePreferences.getObjectMap(key: Preferences.Key<String>): Map<String, V>? {
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.data.card
|
||||
|
||||
import androidx.datastore.preferences.core.MutablePreferences
|
||||
import com.tangem.datasource.local.card.UsedCardInfo
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectList
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectListSync
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -21,23 +23,73 @@ internal class DefaultCardRepository(
|
|||
}
|
||||
|
||||
override suspend fun setCardWasScanned(cardId: String) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val usedCards: List<UsedCardInfo>? = mutablePreferences.getObjectList(
|
||||
key = PreferencesKeys.USED_CARDS_INFO_KEY,
|
||||
)
|
||||
appPreferencesStore.editUsedCards(cardId) { it.copy(isScanned = true) }
|
||||
}
|
||||
|
||||
val updatedUsedCards = usedCards?.updateCard(cardId)
|
||||
?: listOf(UsedCardInfo(cardId = cardId, isScanned = true))
|
||||
override suspend fun startCardActivation(cardId: String) {
|
||||
appPreferencesStore.editUsedCards(cardId) { it.copy(isActivationStarted = true) }
|
||||
}
|
||||
|
||||
mutablePreferences.setObjectList(
|
||||
key = PreferencesKeys.USED_CARDS_INFO_KEY,
|
||||
value = updatedUsedCards,
|
||||
)
|
||||
override suspend fun finishCardActivation(cardId: String) {
|
||||
appPreferencesStore.editUsedCards(cardId) {
|
||||
it.copy(isActivationStarted = true, isActivationFinished = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<UsedCardInfo>.updateCard(cardId: String): List<UsedCardInfo> {
|
||||
val card = find { it.cardId == cardId } ?: UsedCardInfo(cardId = cardId, isScanned = true)
|
||||
return addOrReplace(item = card.copy(isScanned = true), predicate = { it.cardId == cardId })
|
||||
override suspend fun finishCardsActivation(cardIds: List<String>) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val usedCards = mutablePreferences.getUsedCards()
|
||||
|
||||
val updatedUsedCards = cardIds.map { cardId ->
|
||||
usedCards.updateCard(cardId) {
|
||||
it.copy(isActivationStarted = true, isActivationFinished = true)
|
||||
}
|
||||
}
|
||||
|
||||
mutablePreferences.setObjectList(key = PreferencesKeys.USED_CARDS_INFO_KEY, value = updatedUsedCards)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isActivationStarted(cardId: String): Boolean {
|
||||
return getUsedCardSync(cardId)?.isActivationStarted ?: false
|
||||
}
|
||||
|
||||
override suspend fun isActivationFinished(cardId: String): Boolean {
|
||||
return getUsedCardSync(cardId)?.isActivationFinished ?: false
|
||||
}
|
||||
|
||||
override suspend fun isActivationInProgress(cardId: String): Boolean {
|
||||
val card = getUsedCardSync(cardId) ?: return false
|
||||
|
||||
return card.isActivationStarted && !card.isActivationFinished
|
||||
}
|
||||
|
||||
private suspend fun AppPreferencesStore.editUsedCards(cardId: String, update: (UsedCardInfo) -> UsedCardInfo) {
|
||||
editData { mutablePreferences ->
|
||||
val usedCards = mutablePreferences.getUsedCards()
|
||||
|
||||
val updatedUsedCards = usedCards.updateCard(cardId = cardId, update = update)
|
||||
|
||||
mutablePreferences.setObjectList(key = PreferencesKeys.USED_CARDS_INFO_KEY, value = updatedUsedCards)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutablePreferences.getUsedCards(): List<UsedCardInfo> {
|
||||
return with(appPreferencesStore) {
|
||||
getObjectListOrDefault(key = PreferencesKeys.USED_CARDS_INFO_KEY, default = mutableListOf())
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<UsedCardInfo>.updateCard(
|
||||
cardId: String,
|
||||
update: (UsedCardInfo) -> UsedCardInfo,
|
||||
): List<UsedCardInfo> {
|
||||
val card = find { it.cardId == cardId } ?: UsedCardInfo(cardId = cardId)
|
||||
return addOrReplace(item = update(card), predicate = { it.cardId == cardId })
|
||||
}
|
||||
|
||||
private suspend fun getUsedCardSync(cardId: String): UsedCardInfo? {
|
||||
return appPreferencesStore.getObjectListSync<UsedCardInfo>(PreferencesKeys.USED_CARDS_INFO_KEY)
|
||||
.firstOrNull { it.cardId == cardId }
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,7 @@ package com.tangem.data.source.preferences
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.data.source.preferences.adapters.BigDecimalAdapter
|
||||
import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage
|
||||
import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
|
||||
import javax.inject.Inject
|
||||
|
||||
// 🔥FIXME: Only logic to work with preferences must be here, must be separated to repositories
|
||||
|
|
@ -14,21 +11,13 @@ import javax.inject.Inject
|
|||
@Deprecated("Create repository instead")
|
||||
class PreferencesDataSource @Inject internal constructor(applicationContext: Context) {
|
||||
|
||||
val usedCardsPrefStorage: UsedCardsPrefStorage
|
||||
val disclaimerPrefStorage: DisclaimerPrefStorage
|
||||
|
||||
private val preferences: SharedPreferences =
|
||||
applicationContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||
|
||||
private val moshiConverter = MoshiJsonConverter(
|
||||
adapters = listOf(BigDecimalAdapter()) + MoshiJsonConverter.getTangemSdkAdapters(),
|
||||
typedAdapters = MoshiJsonConverter.getTangemSdkTypedAdapters(),
|
||||
)
|
||||
|
||||
init {
|
||||
incrementLaunchCounter()
|
||||
usedCardsPrefStorage = UsedCardsPrefStorage(preferences, moshiConverter)
|
||||
usedCardsPrefStorage.migrate()
|
||||
disclaimerPrefStorage = DisclaimerPrefStorage(preferences)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package com.tangem.data.source.preferences.model
|
||||
|
||||
internal data class DataSourceUsedCardInfo(
|
||||
val cardId: String,
|
||||
val isScanned: Boolean = false,
|
||||
val isActivationStarted: Boolean = false,
|
||||
val isActivationFinished: Boolean = false,
|
||||
)
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package com.tangem.data.source.preferences.model
|
||||
|
||||
internal data class DataSourceUsedCardInfoOld(
|
||||
val cardId: String,
|
||||
val isScanned: Boolean = false,
|
||||
val isActivationStarted: Boolean = false,
|
||||
)
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
package com.tangem.data.source.preferences.storage
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.data.source.preferences.model.DataSourceUsedCardInfo
|
||||
import com.tangem.data.source.preferences.model.DataSourceUsedCardInfoOld
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Deprecated("Create repository instead")
|
||||
class UsedCardsPrefStorage internal constructor(
|
||||
private val preferences: SharedPreferences,
|
||||
private val jsonConverter: MoshiJsonConverter,
|
||||
) {
|
||||
|
||||
private val migrationList = mutableListOf(
|
||||
UserCardInfoToV2(this),
|
||||
)
|
||||
|
||||
internal fun migrate() {
|
||||
migrationList.forEach { it.migrate() }
|
||||
migrationList.clear()
|
||||
}
|
||||
|
||||
fun scanned(cardId: String) {
|
||||
val restoredList = restore()
|
||||
val foundItem = findCardInfo(cardId, restoredList)?.copy(isScanned = true)
|
||||
?: DataSourceUsedCardInfo(cardId, true)
|
||||
|
||||
save(foundItem, restoredList)
|
||||
}
|
||||
|
||||
fun wasScanned(cardId: String): Boolean {
|
||||
return findCardInfo(cardId)?.isScanned ?: false
|
||||
}
|
||||
|
||||
fun activationStarted(cardId: String) {
|
||||
val restoredList = restore()
|
||||
val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = true)
|
||||
?: DataSourceUsedCardInfo(cardId, isActivationStarted = true)
|
||||
|
||||
save(foundItem, restoredList)
|
||||
}
|
||||
|
||||
fun activationFinished(cardId: String) {
|
||||
val restoredList = restore()
|
||||
var foundItem = findCardInfo(cardId, restoredList) ?: DataSourceUsedCardInfo(cardId)
|
||||
foundItem = foundItem.copy(
|
||||
isActivationStarted = true,
|
||||
isActivationFinished = true,
|
||||
)
|
||||
|
||||
save(foundItem, restoredList)
|
||||
}
|
||||
|
||||
fun isActivationStarted(cardId: String): Boolean {
|
||||
return findCardInfo(cardId)?.isActivationStarted ?: false
|
||||
}
|
||||
|
||||
fun isActivationFinished(cardId: String): Boolean {
|
||||
return findCardInfo(cardId)?.isActivationFinished ?: false
|
||||
}
|
||||
|
||||
fun isActivationInProgress(cardId: String): Boolean {
|
||||
val cardInfo = findCardInfo(cardId) ?: return false
|
||||
return cardInfo.isActivationStarted && !cardInfo.isActivationFinished
|
||||
}
|
||||
|
||||
fun hadFinishedActivation(): Boolean {
|
||||
return restore().any { it.isActivationFinished }
|
||||
}
|
||||
|
||||
private fun findCardInfo(
|
||||
cardId: String,
|
||||
list: MutableList<DataSourceUsedCardInfo>? = null,
|
||||
): DataSourceUsedCardInfo? {
|
||||
val findInList = list ?: restore()
|
||||
return findInList.firstOrNull { it.cardId == cardId }
|
||||
}
|
||||
|
||||
private fun save(usedCardInfo: DataSourceUsedCardInfo?, usedCardsInfo: MutableList<DataSourceUsedCardInfo>) {
|
||||
val info = usedCardInfo ?: return
|
||||
|
||||
with(usedCardsInfo) {
|
||||
val index = indexOfFirst { it.cardId == info.cardId }
|
||||
if (index == -1) {
|
||||
add(info)
|
||||
} else {
|
||||
set(index, info)
|
||||
}
|
||||
}
|
||||
|
||||
save(usedCardsInfo)
|
||||
}
|
||||
|
||||
private fun save(list: MutableList<DataSourceUsedCardInfo>) {
|
||||
val json = jsonConverter.toJson(list)
|
||||
preferences.edit { putString(USED_CARDS_INFO_V2, json) }
|
||||
}
|
||||
|
||||
private fun restore(): MutableList<DataSourceUsedCardInfo> {
|
||||
val json = preferences.getString(USED_CARDS_INFO_V2, null) ?: return mutableListOf()
|
||||
return try {
|
||||
jsonConverter.fromJson(json, jsonConverter.typedList(DataSourceUsedCardInfo::class.java))!!
|
||||
} catch (ex: Exception) {
|
||||
preferences.edit(true) { remove(USED_CARDS_INFO_V2) }
|
||||
mutableListOf()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val USED_CARDS_INFO_V2 = "usedCardsInfo_v2"
|
||||
private const val USED_CARDS_INFO = "usedCardsInfo"
|
||||
}
|
||||
|
||||
private class UserCardInfoToV2(
|
||||
private val storage: UsedCardsPrefStorage,
|
||||
) : Migration {
|
||||
override fun migrate() {
|
||||
val restoredCardsInfo = restore()
|
||||
if (restoredCardsInfo.isEmpty()) return
|
||||
|
||||
val newCardsInfo = restoredCardsInfo.map { cardInfo ->
|
||||
DataSourceUsedCardInfo(
|
||||
cardId = cardInfo.cardId,
|
||||
isScanned = cardInfo.isScanned,
|
||||
isActivationStarted = true,
|
||||
isActivationFinished = !cardInfo.isActivationStarted,
|
||||
)
|
||||
}.toMutableList()
|
||||
storage.save(newCardsInfo)
|
||||
}
|
||||
|
||||
private fun restore(): MutableList<DataSourceUsedCardInfoOld> {
|
||||
val json = storage.preferences.getString(USED_CARDS_INFO, null) ?: return mutableListOf()
|
||||
return try {
|
||||
storage.jsonConverter.fromJson(
|
||||
json,
|
||||
storage.jsonConverter.typedList(DataSourceUsedCardInfoOld::class.java),
|
||||
)!!
|
||||
} catch (ex: Exception) {
|
||||
mutableListOf()
|
||||
} finally {
|
||||
storage.preferences.edit(true) { remove(USED_CARDS_INFO) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,4 +7,19 @@ interface CardRepository {
|
|||
fun wasCardScanned(cardId: String): Flow<Boolean>
|
||||
|
||||
suspend fun setCardWasScanned(cardId: String)
|
||||
|
||||
suspend fun startCardActivation(cardId: String)
|
||||
|
||||
suspend fun finishCardActivation(cardId: String)
|
||||
|
||||
suspend fun finishCardsActivation(cardIds: List<String>)
|
||||
|
||||
@Throws
|
||||
suspend fun isActivationStarted(cardId: String): Boolean
|
||||
|
||||
@Throws
|
||||
suspend fun isActivationFinished(cardId: String): Boolean
|
||||
|
||||
@Throws
|
||||
suspend fun isActivationInProgress(cardId: String): Boolean
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue