Updated on 2026-08-14
This commit is contained in:
parent
0d993b5141
commit
e72bf276da
18 changed files with 568 additions and 21 deletions
|
|
@ -18,6 +18,7 @@ import com.tangem.tap.common.DialogManager
|
|||
import com.tangem.tap.common.IntentHandler
|
||||
import com.tangem.tap.common.OnActivityResultCallback
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
|
|
@ -25,6 +26,8 @@ import com.tangem.tap.common.shop.GooglePayService
|
|||
import com.tangem.tap.common.shop.GooglePayService.Companion.LOAD_PAYMENT_DATA_REQUEST_CODE
|
||||
import com.tangem.tap.common.shop.googlepay.GooglePayUtil.createPaymentsClient
|
||||
import com.tangem.tap.domain.TangemSdkManager
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.di.provideDummyImplementation
|
||||
import com.tangem.tap.features.shop.redux.ShopAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -40,6 +43,7 @@ import kotlin.coroutines.CoroutineContext
|
|||
lateinit var tangemSdk: TangemSdk
|
||||
lateinit var tangemSdkManager: TangemSdkManager
|
||||
lateinit var backupService: BackupService
|
||||
lateinit var userWalletsListManager: UserWalletsListManager
|
||||
var notificationsHandler: NotificationsHandler? = null
|
||||
|
||||
private val coroutineContext: CoroutineContext
|
||||
|
|
@ -73,6 +77,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
tangemSdkManager = TangemSdkManager(tangemSdk, this)
|
||||
appStateHolder.tangemSdkManager = tangemSdkManager
|
||||
backupService = BackupService.init(tangemSdk, this)
|
||||
userWalletsListManager = UserWalletsListManager.provideDummyImplementation()
|
||||
|
||||
store.dispatch(
|
||||
ShopAction.CheckIfGooglePayAvailable(
|
||||
|
|
@ -82,7 +87,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
|
||||
private fun systemActions() {
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
val windowInsetsController = WindowInsetsControllerCompat(window, binding.root)
|
||||
|
|
@ -106,7 +110,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
val isOnboardingServiceActive = store.state.globalState.onboardingState.onboardingStarted
|
||||
val shopOpened = store.state.shopState.total != null
|
||||
if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore && !shopOpened)) {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Home))
|
||||
val navigateTo = if (userWalletsListManager.hasSavedUserWallets) AppScreen.Welcome else AppScreen.Home
|
||||
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(navigateTo))
|
||||
}
|
||||
intentHandler.handleIntent(intent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.tap.common.redux.StateDialog
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -26,6 +27,11 @@ fun Store<*>.dispatchNotification(resId: Int) {
|
|||
dispatchOnMain(GlobalAction.ShowNotification(resId))
|
||||
}
|
||||
|
||||
@Suppress("unused") // receiver type
|
||||
fun Store<*>.onUserWalletSelected(userWallet: UserWallet, refresh: Boolean = false) {
|
||||
// TODO: Load tokens for selected user wallet. Will be created in further MRs
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchToastNotification(resId: Int) {
|
||||
dispatchOnMain(GlobalAction.ShowToastNotification(resId))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.tap.features.shop.redux.ShopReducer
|
|||
import com.tangem.tap.features.tokens.redux.TokensReducer
|
||||
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import org.rekotlin.Action
|
||||
|
||||
fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder): AppState {
|
||||
|
|
@ -36,6 +37,7 @@ fun appReducer(action: Action, state: AppState?, appStateHolder: AppStateHolder)
|
|||
tokensState = TokensReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
shopState = ShopReducer.reduce(action, state.shopState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,9 @@ import com.tangem.tap.features.tokens.redux.TokensMiddleware
|
|||
import com.tangem.tap.features.tokens.redux.TokensState
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.WalletMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import com.tangem.tap.features.walletSelector.redux.WalletSelectorState
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import org.rekotlin.Middleware
|
||||
import org.rekotlin.StateType
|
||||
|
||||
|
|
@ -84,6 +85,7 @@ data class AppState(
|
|||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
ShopMiddleware().shopMiddleware,
|
||||
WelcomeMiddleware().middleware,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ package com.tangem.tap.common.redux.navigation
|
|||
|
||||
import android.content.Intent
|
||||
import com.tangem.tap.common.CustomTabsManager
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.openFragment
|
||||
import com.tangem.tap.common.extensions.popBackTo
|
||||
import com.tangem.tap.common.extensions.shareText
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
val navigationMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
val navigationMiddleware: Middleware<AppState> = { _, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is NavigationAction) {
|
||||
|
|
@ -16,16 +18,24 @@ val navigationMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
when (action) {
|
||||
is NavigationAction.NavigateTo -> {
|
||||
navState?.activity?.get()?.openFragment(
|
||||
action.screen,
|
||||
action.addToBackstack,
|
||||
action.fragmentShareTransition
|
||||
screen = action.screen,
|
||||
addToBackstack = action.addToBackstack,
|
||||
fgShareTransition = action.fragmentShareTransition,
|
||||
)
|
||||
}
|
||||
is NavigationAction.PopBackTo -> {
|
||||
if (action.screen == AppScreen.Home) {
|
||||
navState?.activity?.get()?.popBackTo(null, true)
|
||||
} else {
|
||||
navState?.activity?.get()?.popBackTo(action.screen)
|
||||
when (val screen = action.screen) {
|
||||
AppScreen.Home,
|
||||
AppScreen.Welcome,
|
||||
-> {
|
||||
navState?.activity?.get()?.popBackTo(screen = null, inclusive = true)
|
||||
if (navState?.backStack?.contains(screen) != true) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
navState?.activity?.get()?.popBackTo(screen = action.screen)
|
||||
}
|
||||
}
|
||||
}
|
||||
is NavigationAction.OpenUrl -> {
|
||||
|
|
@ -39,10 +49,11 @@ val navigationMiddleware: Middleware<AppState> = { dispatch, state ->
|
|||
navState?.activity?.get()?.startActivity(intent)
|
||||
}
|
||||
is NavigationAction.Share -> {
|
||||
navState?.activity?.get()?.let {
|
||||
it.shareText(action.data)
|
||||
}
|
||||
navState?.activity?.get()?.shareText(action.data)
|
||||
}
|
||||
is NavigationAction.ActivityCreated,
|
||||
is NavigationAction.ActivityDestroyed,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import org.rekotlin.StateType
|
|||
import java.lang.ref.WeakReference
|
||||
|
||||
data class NavigationState(
|
||||
val backStack: List<AppScreen> = listOf(AppScreen.Home),
|
||||
val backStack: List<AppScreen> = emptyList(),
|
||||
val activity: WeakReference<AppCompatActivity>? = null,
|
||||
) : StateType
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.CardFilter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.UserCodeType
|
||||
import com.tangem.common.card.FirmwareVersion
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.Config
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.common.map
|
||||
|
|
@ -46,7 +48,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
userTokensRepository: UserTokensRepository,
|
||||
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
messageRes: Int? = null,
|
||||
useBiometricsForAccessCode: Boolean = false,
|
||||
): CompletionResult<ScanResponse> {
|
||||
setAccessCodeRequestPolicy(useBiometricsForAccessCode)
|
||||
|
||||
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
|
||||
return runTaskAsyncReturnOnMain(
|
||||
runnable = ScanProductTask(null, userTokensRepository, additionalBlockchainsToDerive),
|
||||
|
|
@ -175,6 +180,16 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
return context.getString(stringResId, formatArgs)
|
||||
}
|
||||
|
||||
fun setAccessCodeRequestPolicy(
|
||||
useBiometricsForAccessCode: Boolean,
|
||||
) {
|
||||
tangemSdk.config.userCodeRequestPolicy = if (useBiometricsForAccessCode) {
|
||||
UserCodeRequestPolicy.AlwaysWithBiometrics(codeType = UserCodeType.AccessCode)
|
||||
} else {
|
||||
UserCodeRequestPolicy.Default
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val config = Config(
|
||||
linkedTerminal = true,
|
||||
|
|
|
|||
34
app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt
Normal file
34
app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.tap.domain.model
|
||||
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.domain.common.util.userWalletId
|
||||
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
||||
|
||||
data class UserWallet(
|
||||
val name: String,
|
||||
val walletId: UserWalletId,
|
||||
val artworkUrl: String,
|
||||
val cardsInWallet: Set<String>,
|
||||
val scanResponse: ScanResponse,
|
||||
) {
|
||||
val cardId: String
|
||||
get() = scanResponse.card.cardId
|
||||
|
||||
companion object {
|
||||
suspend operator fun invoke(
|
||||
scanResponse: ScanResponse,
|
||||
backupCardsIds: Set<String>? = null,
|
||||
): UserWallet {
|
||||
return with(scanResponse) {
|
||||
UserWallet(
|
||||
walletId = card.userWalletId,
|
||||
name = productType.name,
|
||||
artworkUrl = card.getOrLoadCardArtworkUrl(),
|
||||
cardsInWallet = backupCardsIds?.plus(card.cardId) ?: setOf(card.cardId),
|
||||
scanResponse = this,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
package com.tangem.tap.domain.scanCard
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.BatchIdParamsInterceptor
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.primaryCardIsSaltPayVisa
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerType
|
||||
import com.tangem.tap.features.disclaimer.redux.isAccepted
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.OnboardingSaltPayHelper
|
||||
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.saltPay.SaltPayExceptionHandler
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.redux.OnboardingSaltPayState
|
||||
import com.tangem.tap.preferencesStorage
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.tap.userTokensRepository
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
// TODO: Create repository for that
|
||||
object ScanCardProcessor {
|
||||
suspend fun scan(
|
||||
useBiometricsForAccessCode: Boolean = false,
|
||||
additionalBlockchainsToDerive: Collection<Blockchain>? = null,
|
||||
onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {},
|
||||
onScanStateChange: suspend (scanInProgress: Boolean) -> Unit = {},
|
||||
onWalletNotCreated: suspend (() -> Unit) = {},
|
||||
onFailure: suspend (error: TangemError) -> Unit = {},
|
||||
onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {},
|
||||
) = withMainContext {
|
||||
onProgressStateChange(true)
|
||||
onScanStateChange(true)
|
||||
tangemSdkManager.scanProduct(
|
||||
userTokensRepository = userTokensRepository,
|
||||
additionalBlockchainsToDerive = additionalBlockchainsToDerive,
|
||||
useBiometricsForAccessCode = useBiometricsForAccessCode,
|
||||
)
|
||||
.doOnFailure { error ->
|
||||
onProgressStateChange(false)
|
||||
onScanStateChange(false)
|
||||
onFailure(error)
|
||||
}
|
||||
.doOnSuccess { scanResponse ->
|
||||
onScanStateChange(false)
|
||||
checkForUnfinishedBackupForSaltPay(
|
||||
backupService = backupService,
|
||||
scanResponse = scanResponse,
|
||||
onProgressStateChange = { onProgressStateChange(it) },
|
||||
nextHandler = {
|
||||
showDisclaimerIfNeed(
|
||||
scanResponse = scanResponse,
|
||||
nextHandler = {
|
||||
onScanSuccess(
|
||||
scanResponse = scanResponse,
|
||||
onProgressStateChange = onProgressStateChange,
|
||||
onSuccess = onSuccess,
|
||||
onWalletNotCreated = onWalletNotCreated,
|
||||
onFailure = onFailure,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It checks only the SaltPay cards. To check for unfinished backups for the standard Wallet cards
|
||||
* see BackupAction.CheckForUnfinishedBackup
|
||||
* If user touches card other than Visa SaltPay - show dialog and block next processing
|
||||
*/
|
||||
private inline fun checkForUnfinishedBackupForSaltPay(
|
||||
backupService: BackupService,
|
||||
scanResponse: ScanResponse,
|
||||
onProgressStateChange: (showProgress: Boolean) -> Unit,
|
||||
nextHandler: (ScanResponse) -> Unit,
|
||||
) {
|
||||
if (!backupService.hasIncompletedBackup || !backupService.primaryCardIsSaltPayVisa()) {
|
||||
nextHandler(scanResponse)
|
||||
return
|
||||
}
|
||||
|
||||
val isTheSamePrimaryCard = backupService.primaryCardId
|
||||
?.let { it == scanResponse.card.cardId }
|
||||
?: false
|
||||
|
||||
if (scanResponse.isSaltPayWallet() || !isTheSamePrimaryCard) {
|
||||
onProgressStateChange(false)
|
||||
showSaltPayTapVisaLogoCardDialog()
|
||||
} else {
|
||||
nextHandler(scanResponse)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun showDisclaimerIfNeed(
|
||||
scanResponse: ScanResponse,
|
||||
crossinline nextHandler: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
val disclaimerType = DisclaimerType.get(scanResponse)
|
||||
store.dispatch(DisclaimerAction.SetDisclaimerType(disclaimerType))
|
||||
|
||||
if (disclaimerType.isAccepted()) {
|
||||
nextHandler((scanResponse))
|
||||
} else scope.launch(Dispatchers.Main) {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatch(
|
||||
DisclaimerAction.Show {
|
||||
scope.launch {
|
||||
nextHandler(scanResponse)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun onScanSuccess(
|
||||
scanResponse: ScanResponse,
|
||||
crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit,
|
||||
crossinline onWalletNotCreated: suspend () -> Unit,
|
||||
crossinline onSuccess: suspend (ScanResponse) -> Unit,
|
||||
crossinline onFailure: suspend (error: TangemError) -> Unit,
|
||||
) {
|
||||
Analytics.send(IntroductionProcess.CardWasScanned())
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val tapWalletManager = globalState.tapWalletManager
|
||||
tapWalletManager.updateConfigManager(scanResponse)
|
||||
|
||||
Analytics.addParamsInterceptor(BatchIdParamsInterceptor(scanResponse.card.batchId))
|
||||
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
|
||||
if (scanResponse.isSaltPay()) {
|
||||
if (scanResponse.isSaltPayVisa()) {
|
||||
val (manager, config) = OnboardingSaltPayState.initDependency(scanResponse)
|
||||
val result = OnboardingSaltPayHelper.isOnboardingCase(scanResponse, manager)
|
||||
delay(500)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is Result.Success -> {
|
||||
val isOnboardingCase = result.data
|
||||
if (isOnboardingCase) {
|
||||
onWalletNotCreated()
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatch(OnboardingSaltPayAction.SetDependencies(manager, config))
|
||||
store.dispatch(OnboardingSaltPayAction.Update)
|
||||
navigateTo(AppScreen.OnboardingWallet) { onProgressStateChange(it) }
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
onSuccess(scanResponse)
|
||||
onProgressStateChange(false)
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
SaltPayExceptionHandler.handle(result.error)
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
onFailure(TangemSdkError.ExceptionError(result.error))
|
||||
onProgressStateChange(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
if (scanResponse.card.backupStatus?.isActive == false) {
|
||||
showSaltPayTapVisaLogoCardDialog()
|
||||
} else {
|
||||
onSuccess(scanResponse)
|
||||
}
|
||||
onProgressStateChange(false)
|
||||
}
|
||||
} else {
|
||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
onWalletNotCreated()
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = true))
|
||||
val appScreen = OnboardingHelper.whereToNavigate(scanResponse)
|
||||
navigateTo(appScreen) { onProgressStateChange(it) }
|
||||
} else {
|
||||
if (scanResponse.twinsIsTwinned() && !preferencesStorage.wasTwinsOnboardingShown()) {
|
||||
onWalletNotCreated()
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly))
|
||||
navigateTo(AppScreen.OnboardingTwins) { onProgressStateChange(it) }
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
onSuccess(scanResponse)
|
||||
onProgressStateChange(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSaltPayTapVisaLogoCardDialog() {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.saltpay_error_empty_backup_title,
|
||||
messageId = R.string.saltpay_error_empty_backup_message,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend inline fun navigateTo(
|
||||
screen: AppScreen,
|
||||
onProgressStateChange: (showProgress: Boolean) -> Unit,
|
||||
) {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen))
|
||||
onProgressStateChange(false)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.tap.domain.userWalletList
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface UserWalletsListManager {
|
||||
val userWallets: Flow<List<UserWallet>>
|
||||
val selectedUserWallet: Flow<UserWallet>
|
||||
val selectedUserWalletSync: UserWallet?
|
||||
val isLocked: Flow<Boolean>
|
||||
val isLockedSync: Boolean
|
||||
val hasSavedUserWallets: Boolean
|
||||
|
||||
suspend fun unlockWithBiometry(): CompletionResult<UserWallet?>
|
||||
suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult<Unit>
|
||||
fun lock()
|
||||
|
||||
suspend fun selectWallet(walletId: UserWalletId): CompletionResult<UserWallet>
|
||||
|
||||
suspend fun save(userWallet: UserWallet): CompletionResult<Unit>
|
||||
suspend fun update(userWallet: UserWallet): CompletionResult<Unit>
|
||||
|
||||
suspend fun delete(walletIds: List<UserWalletId>): CompletionResult<Unit>
|
||||
suspend fun clear(): CompletionResult<Unit>
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.tap.domain.userWalletList.di
|
||||
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.implementation.DummyUserWalletsListManager
|
||||
|
||||
fun UserWalletsListManager.Companion.provideDummyImplementation(): UserWalletsListManager {
|
||||
return DummyUserWalletsListManager()
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.tap.domain.userWalletList.implementation
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.catching
|
||||
import com.tangem.domain.common.util.UserWalletId
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.userWalletList.UserWalletsListManager
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
class DummyUserWalletsListManager : UserWalletsListManager {
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = flowOf(emptyList())
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = flowOf()
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = null
|
||||
override val isLocked: Flow<Boolean>
|
||||
get() = flowOf(true)
|
||||
override val isLockedSync: Boolean
|
||||
get() = true
|
||||
override val hasSavedUserWallets: Boolean
|
||||
get() = false
|
||||
|
||||
override suspend fun unlockWithBiometry(): CompletionResult<UserWallet?> {
|
||||
return CompletionResult.Success(null)
|
||||
}
|
||||
|
||||
override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override fun lock() {
|
||||
/* no-op */
|
||||
}
|
||||
|
||||
override suspend fun selectWallet(walletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return catching {
|
||||
error("Not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun update(userWallet: UserWallet): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun delete(walletIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.navigation.AppScreen
|
||||
import com.tangem.tap.common.redux.navigation.NavigationAction
|
||||
import com.tangem.tap.domain.model.UserWallet
|
||||
import com.tangem.tap.domain.scanCard.ScanCardProcessor
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.userWalletsListManager
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
internal class WelcomeMiddleware {
|
||||
val middleware: Middleware<AppState> = { _, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is WelcomeAction) {
|
||||
handleAction(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAction(action: WelcomeAction) {
|
||||
when (action) {
|
||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometry()
|
||||
is WelcomeAction.ProceedWithCard -> proceedWithCard()
|
||||
is WelcomeAction.ProceedWithBiometrics.Error -> Unit
|
||||
is WelcomeAction.ProceedWithCard.Error -> Unit
|
||||
is WelcomeAction.ProceedWithBiometrics.Success -> Unit
|
||||
is WelcomeAction.ProceedWithCard.Success -> Unit
|
||||
is WelcomeAction.CloseError -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithBiometry() {
|
||||
scope.launch {
|
||||
userWalletsListManager.unlockWithBiometry()
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Error(error))
|
||||
}
|
||||
.doOnSuccess { selectedUserWallet ->
|
||||
if (selectedUserWallet != null) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(selectedUserWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithCard() = scope.launch {
|
||||
scanCardInternal { scanResponse ->
|
||||
val userWallet = UserWallet(scanResponse)
|
||||
|
||||
userWalletsListManager.unlockWithCard(userWallet)
|
||||
.doOnFailure { error ->
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success)
|
||||
store.onUserWalletSelected(userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun scanCardInternal(
|
||||
crossinline onCardScanned: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
ScanCardProcessor.scan(
|
||||
onSuccess = { scanResponse ->
|
||||
scope.launch { onCardScanned(scanResponse) }
|
||||
},
|
||||
onFailure = {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(it))
|
||||
},
|
||||
onWalletNotCreated = {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
internal object WelcomeReducer {
|
||||
fun reduce(action: Action, state: AppState): WelcomeState {
|
||||
return if (action is WelcomeAction) {
|
||||
internalReduce(action, state.welcomeState)
|
||||
} else state.welcomeState
|
||||
}
|
||||
|
||||
private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState {
|
||||
return when (action) {
|
||||
is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true)
|
||||
is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true)
|
||||
is WelcomeAction.ProceedWithBiometrics.Error -> state.copy(
|
||||
error = action.error,
|
||||
isUnlockWithBiometricsInProgress = false,
|
||||
)
|
||||
is WelcomeAction.ProceedWithCard.Error -> state.copy(
|
||||
error = action.error,
|
||||
isUnlockWithCardInProgress = false,
|
||||
)
|
||||
is WelcomeAction.ProceedWithBiometrics.Success -> state.copy(isUnlockWithBiometricsInProgress = false)
|
||||
is WelcomeAction.ProceedWithCard.Success -> state.copy(isUnlockWithCardInProgress = false)
|
||||
is WelcomeAction.CloseError -> state.copy(error = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import org.rekotlin.StateType
|
||||
|
||||
class WelcomeState : StateType
|
||||
data class WelcomeState(
|
||||
val isUnlockWithBiometricsInProgress: Boolean = false,
|
||||
val isUnlockWithCardInProgress: Boolean = false,
|
||||
val error: TangemError? = null,
|
||||
) : StateType
|
||||
|
|
@ -20,6 +20,7 @@ import androidx.fragment.app.viewModels
|
|||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.fragments.ComposeFragment
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.welcome.ui.components.WelcomeScreenContent
|
||||
import com.tangem.wallet.R
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ internal class WelcomeFragment : ComposeFragment<WelcomeScreenState>() {
|
|||
state: WelcomeScreenState,
|
||||
) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage)
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
|
||||
val backgroundColor = colorResource(id = R.color.background_primary)
|
||||
SystemBarsEffect {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package com.tangem.tap.features.welcome.ui
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
|
||||
@Immutable
|
||||
internal data class WelcomeScreenState(
|
||||
val showUnlockWithBiometricsProgress: Boolean = false,
|
||||
val showUnlockWithCardProgress: Boolean = false,
|
||||
val error: TangemError? = null,
|
||||
val error: TextReference? = null,
|
||||
)
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.features.welcome.ui
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -15,6 +17,7 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
|
|||
|
||||
init {
|
||||
subscribeToStoreChanges()
|
||||
initGlobalState()
|
||||
}
|
||||
|
||||
fun unlockWallets() {
|
||||
|
|
@ -31,8 +34,16 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
|
|||
|
||||
override fun newState(state: WelcomeState) {
|
||||
stateInternal.update { prevState ->
|
||||
// TODO: Update screen state
|
||||
prevState
|
||||
prevState.copy(
|
||||
showUnlockWithBiometricsProgress = state.isUnlockWithBiometricsInProgress,
|
||||
showUnlockWithCardProgress = state.isUnlockWithCardInProgress,
|
||||
error = state.error
|
||||
?.takeUnless { it.silent }
|
||||
?.let { error ->
|
||||
error.messageResId?.let { TextReference.Res(it) }
|
||||
?: TextReference.Str(error.customMessage)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,4 +57,10 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber<WelcomeState> {
|
|||
.select { it.welcomeState }
|
||||
}
|
||||
}
|
||||
|
||||
private fun initGlobalState() {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
store.dispatch(GlobalAction.ExchangeManager.Init)
|
||||
store.dispatch(GlobalAction.FetchUserCountry)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue