Updated on 2026-08-14

This commit is contained in:
Tangem 2021-09-23 12:49:34 +03:00
parent 99f1b91bae
commit a7fc5e1036
38 changed files with 1486 additions and 195 deletions

View file

@ -16,9 +16,12 @@ import com.tangem.tap.features.details.ui.walletconnect.QrScanFragment
import com.tangem.tap.features.details.ui.walletconnect.WalletConnectSessionsFragment
import com.tangem.tap.features.disclaimer.ui.DisclaimerFragment
import com.tangem.tap.features.home.HomeFragment
import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment
import com.tangem.tap.features.onboarding.products.old.OnboardingOtherCardsFragment
import com.tangem.tap.features.onboarding.products.wallet.OnboardingWalletFragment
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.tokens.ui.AddTokensFragment
import com.tangem.tap.features.wallet.ui.TwinsOnboardingFragment
import com.tangem.tap.features.wallet.ui.OnboardingTwinsFragment
import com.tangem.tap.features.wallet.ui.WalletDetailsFragment
import com.tangem.tap.features.wallet.ui.WalletFragment
import com.tangem.wallet.R
@ -67,6 +70,10 @@ fun FragmentActivity.addOnBackPressedDispatcher(
private fun fragmentFactory(screen: AppScreen): Fragment {
return when (screen) {
AppScreen.Home -> HomeFragment()
AppScreen.OnboardingNote -> OnboardingNoteFragment()
AppScreen.OnboardingWallet -> OnboardingWalletFragment()
AppScreen.OnboardingTwins -> OnboardingTwinsFragment()
AppScreen.OnboardingOther -> OnboardingOtherCardsFragment()
AppScreen.Wallet -> WalletFragment()
AppScreen.Send -> SendFragment()
AppScreen.Details -> DetailsFragment()
@ -75,7 +82,6 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AppScreen.Disclaimer -> DisclaimerFragment()
AppScreen.CreateTwinWalletWarning -> TwinWalletWarningFragment()
AppScreen.CreateTwinWallet -> CreateTwinWalletFragment()
AppScreen.TwinsOnboarding -> TwinsOnboardingFragment()
AppScreen.AddTokens -> AddTokensFragment()
AppScreen.WalletDetails -> WalletDetailsFragment()
AppScreen.WalletConnectSessions -> WalletConnectSessionsFragment()

View file

@ -6,6 +6,9 @@ import com.tangem.tap.features.details.redux.DetailsReducer
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer
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.wallet.redux.OnboardingWalletReducer
import com.tangem.tap.features.onboarding.redux.OnboardingReducer
import com.tangem.tap.features.send.redux.reducers.SendScreenReducer
import com.tangem.tap.features.tokens.redux.TokensReducer
import com.tangem.tap.features.wallet.redux.reducers.WalletReducer
@ -19,6 +22,9 @@ fun appReducer(action: Action, state: AppState?): AppState {
navigationState = NavigationReducer.reduce(action, state),
globalState = globalReducer(action, state),
homeState = HomeReducer.reduce(action, state),
onboardingState = OnboardingReducer.reduce(action, state),
onboardingNoteState = OnboardingNoteReducer.reduce(action, state),
onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
walletState = WalletReducer.reduce(action, state),
sendState = SendScreenReducer.reduce(action, state.sendState),
detailsState = DetailsReducer.reduce(action, state),

View file

@ -1,7 +1,7 @@
package com.tangem.tap.common.redux
import com.tangem.tap.common.redux.global.GlobalMiddleware
import com.tangem.tap.common.redux.global.GlobalState
import com.tangem.tap.common.redux.global.globalMiddleware
import com.tangem.tap.common.redux.navigation.NavigationState
import com.tangem.tap.common.redux.navigation.navigationMiddleware
import com.tangem.tap.features.details.redux.DetailsMiddleware
@ -12,6 +12,12 @@ import com.tangem.tap.features.disclaimer.redux.DisclaimerMiddleware
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
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.wallet.redux.OnboardingWalletMiddleware
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.redux.OnboardingMiddleware
import com.tangem.tap.features.onboarding.redux.OnboardingState
import com.tangem.tap.features.send.redux.middlewares.SendMiddleware
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.tokens.redux.TokensMiddleware
@ -25,6 +31,9 @@ data class AppState(
val navigationState: NavigationState = NavigationState(),
val globalState: GlobalState = GlobalState(),
val homeState: HomeState = HomeState(),
val onboardingState: OnboardingState = OnboardingState(),
val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(),
val walletState: WalletState = WalletState(),
val sendState: SendState = SendState(),
val detailsState: DetailsState = DetailsState(),
@ -36,8 +45,12 @@ data class AppState(
companion object {
fun getMiddleware(): List<Middleware<AppState>> {
return listOf(
logMiddleware, navigationMiddleware, notificationsMiddleware, globalMiddleware,
HomeMiddleware().homeMiddleware,
logMiddleware, navigationMiddleware, notificationsMiddleware,
GlobalMiddleware.handler,
HomeMiddleware.handler,
OnboardingMiddleware.handler,
OnboardingNoteMiddleware.handler,
OnboardingWalletMiddleware.handler,
WalletMiddleware().walletMiddleware,
SendMiddleware().sendMiddleware,
DetailsMiddleware().detailsMiddleware,

View file

@ -2,6 +2,9 @@ package com.tangem.tap.common.redux.global
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemError
import com.tangem.tap.common.redux.DebugErrorAction
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.configurable.config.ConfigManager
import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
@ -14,6 +17,14 @@ import org.rekotlin.Action
sealed class GlobalAction : Action {
data class DebugShowError(override val error: TapError) : GlobalAction(), DebugErrorAction
data class ReadCard(
val onSuccess: (suspend (ScanNoteResponse) -> Unit)? = null,
val onFailure: (suspend (TangemError) -> Unit)? = null,
val messageResId: Int? = null,
) : GlobalAction()
object ScanFailsCounter {
data class ChooseBehavior(val result: CompletionResult<ScanNoteResponse>) : GlobalAction()
object Reset : GlobalAction()

View file

@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
@ -13,10 +14,19 @@ import com.tangem.tap.network.moonpay.MoonpayService
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Middleware
val globalMiddleware: Middleware<AppState> = { dispatch, appState ->
class GlobalMiddleware {
companion object {
val handler = globalMiddlewareHandler
}
}
private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState ->
{ nextDispatch ->
{ action ->
when (action) {
@ -78,6 +88,21 @@ val globalMiddleware: Middleware<AppState> = { dispatch, appState ->
}
}
}
is GlobalAction.ReadCard -> {
scope.launch {
val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler, action.messageResId)
withContext(Dispatchers.Main) {
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
when (result) {
is CompletionResult.Success -> {
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
action.onSuccess?.invoke(result.data)
}
is CompletionResult.Failure -> action.onFailure?.invoke(result.error)
}
}
}
}
}
nextDispatch(action)
}

View file

@ -2,6 +2,9 @@ package com.tangem.tap.common.redux.navigation
import android.view.View
import androidx.fragment.app.FragmentActivity
import androidx.transition.ChangeBounds
import androidx.transition.ChangeImageTransform
import androidx.transition.ChangeTransform
import androidx.transition.TransitionSet
import org.rekotlin.Action
import java.lang.ref.WeakReference
@ -27,4 +30,14 @@ data class FragmentShareTransition(
val exitTransitionSet: TransitionSet
)
data class ShareElement(val wView: WeakReference<View>, val name: String)
data class ShareElement(val wView: WeakReference<View>, val name: String)
//TODO: remove transition example
class DetailsTransition : TransitionSet() {
init {
ordering = ORDERING_TOGETHER;
addTransition(ChangeBounds())
.addTransition(ChangeTransform())
.addTransition(ChangeImageTransform())
}
}

View file

@ -5,12 +5,19 @@ import org.rekotlin.StateType
import java.lang.ref.WeakReference
data class NavigationState(
val backStack: List<AppScreen> = listOf(AppScreen.Home),
val activity: WeakReference<FragmentActivity>? = null
val backStack: List<AppScreen> = listOf(AppScreen.Home),
val activity: WeakReference<FragmentActivity>? = null
) : StateType
enum class AppScreen {
Home, Wallet, WalletDetails, Send, Details, DetailsConfirm, DetailsSecurity, Disclaimer,
CreateTwinWalletWarning, CreateTwinWallet, TwinsOnboarding, AddTokens, WalletConnectSessions,
Home,
Disclaimer,
OnboardingNote, OnboardingWallet, OnboardingTwins, OnboardingOther,
Wallet, WalletDetails,
Send,
Details, DetailsConfirm, DetailsSecurity,
CreateTwinWalletWarning, CreateTwinWallet,
AddTokens,
WalletConnectSessions,
QrScan
}

View file

@ -1,8 +1,9 @@
package com.tangem.tap.features.disclaimer.redux
import com.tangem.tap.common.post
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.features.home.redux.HomeAction
import com.tangem.tap.preferencesStorage
import com.tangem.tap.store
import org.rekotlin.Middleware
@ -14,16 +15,10 @@ class DisclaimerMiddleware {
when (action) {
is DisclaimerAction.AcceptDisclaimer -> {
preferencesStorage.saveDisclaimerAccepted()
if (store.state.walletState.twinCardsState != null) {
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
if (showOnboarding) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
} else {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
} else {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
store.dispatch(NavigationAction.PopBackTo())
//delayed sending used for better animation
post(550) { store.dispatch(HomeAction.ReadCard) }
}
}
next(action)

View file

@ -1,12 +1,20 @@
package com.tangem.tap.features.home
import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.transition.TransitionInflater
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.extensions.safeStartActivity
import com.tangem.tap.common.extensions.toast
import com.tangem.tap.common.redux.global.StateDialog
import com.tangem.tap.common.redux.navigation.DetailsTransition
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.common.redux.navigation.ShareElement
import com.tangem.tap.common.toggleWidget.IndeterminateProgressButtonWidget
import com.tangem.tap.common.toggleWidget.ViewStateWidget
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.home.redux.HomeDialog
import com.tangem.tap.features.home.redux.HomeState
@ -15,11 +23,36 @@ import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_home.*
import org.rekotlin.StoreSubscriber
import java.lang.ref.WeakReference
class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState> {
lateinit var btnScanCard: ViewStateWidget
private var dialog: Dialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inflater = TransitionInflater.from(requireContext())
exitTransition = inflater.inflateTransition(R.transition.fade)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val transitionViews = mutableListOf<ShareElement>()
transitionViews.add(ShareElement(WeakReference(imv_front_card), "imv_front_card"))
val shareTransition = FragmentShareTransition(transitionViews, DetailsTransition(), DetailsTransition())
store.dispatch(HomeAction.SetFragmentShareTransition(shareTransition))
store.dispatch(HomeAction.Init)
btn_scan_card?.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
btn_get_new_card?.setOnClickListener { store.dispatch(HomeAction.GoToShop) }
btnScanCard = IndeterminateProgressButtonWidget(btn_scan_card, progress)
}
override fun onStart() {
super.onStart()
store.subscribe(this) { state ->
@ -37,16 +70,23 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
override fun newState(state: HomeState) {
if (activity == null) return
if (state.firstLaunch) {
tv_home_description.text = getText(R.string.home_welcome)
btn_shop.text = getText(R.string.common_no)
btn_yes.text = getText(R.string.home_button_yes)
} else {
tv_home_description.text = getText(R.string.home_welcome_back)
btn_shop.text = getText(R.string.home_button_shop)
btn_yes.text = getText(R.string.home_button_scan)
}
handleOpenUrl(state)
handleDialog(state.dialog)
btnScanCard.changeState(state.btnScanState.progressState)
btnScanCard.mainView.isEnabled = state.btnScanState.enabled
}
private fun handleOpenUrl(state: HomeState) {
if (state.openUrl == null) return
val context = context ?: return
val uri = Uri.parse(state.openUrl)
val intent = Intent(Intent.ACTION_VIEW, uri)
context.safeStartActivity(intent, null, { toast(it.toString()) }, {
store.dispatch(HomeAction.SetOpenUrl(null))
})
}
private fun handleDialog(stateDialog: StateDialog?) {
@ -61,19 +101,8 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber<HomeState
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn_yes?.setOnClickListener { store.dispatch(HomeAction.ReadCard) }
btn_shop?.setOnClickListener { store.dispatch(HomeAction.GoToShop(requireContext())) }
store.dispatch(GlobalAction.GetMoonPayUserStatus)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val inflater = TransitionInflater.from(requireContext())
exitTransition = inflater.inflateTransition(R.transition.fade)
override fun onDestroyView() {
store.dispatch(HomeAction.SetFragmentShareTransition(null))
super.onDestroyView()
}
}

View file

@ -1,18 +1,22 @@
package com.tangem.tap.features.home.redux
import android.content.Context
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import org.rekotlin.Action
sealed class HomeAction : Action {
object Init : HomeAction()
data class SetFragmentShareTransition(val shareTransition: FragmentShareTransition?) : HomeAction()
object ReadCard : HomeAction()
data class GoToShop(val context: Context) : HomeAction()
object CheckIfFirstLaunch : HomeAction() {
data class Result(val firstLaunch: Boolean) : HomeAction()
}
object GoToShop : HomeAction()
class SetOpenUrl(val url: String?) : HomeAction()
class SetTermsOfUseState(val isDisclaimerAccepted: Boolean) : HomeAction()
data class ChangeScanCardButtonState(val state: IndeterminateProgressButton) : HomeAction()
object ShowDialog : HomeAction() {
object ScanFails : HomeAction()
}
object HideDialog : HomeAction()
}

View file

@ -1,80 +1,82 @@
package com.tangem.tap.features.home.redux
import android.content.Intent
import android.net.Uri
import androidx.core.content.ContextCompat.startActivity
import com.tangem.common.CompletionResult
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.redux.AppState
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.domain.TapError
import com.tangem.tap.features.onboarding.OnboardingService
import com.tangem.tap.features.onboarding.redux.OnboardingAction
import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Middleware
class HomeMiddleware {
val homeMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
when (action) {
is HomeAction.CheckIfFirstLaunch -> {
store.dispatch(
HomeAction.CheckIfFirstLaunch.Result(preferencesStorage.getCountOfLaunches() == 1)
)
}
is HomeAction.ReadCard -> {
scope.launch {
val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler)
withContext(Dispatchers.Main) {
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
when (result) {
is CompletionResult.Success -> {
tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card)
store.dispatch(GlobalAction.RestoreAppCurrency)
store.state.globalState.tapWalletManager.onCardScanned(result.data)
showDisclaimerOrNavigateToWallet()
}
}
}
}
}
is HomeAction.GoToShop -> {
val uri = Uri.parse(CARD_SHOP_URI)
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(action.context, intent, null)
}
}
next(action)
}
}
}
private fun showDisclaimerOrNavigateToWallet() {
if (!preferencesStorage.wasDisclaimerAccepted()) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
return
}
if (store.state.walletState.twinCardsState != null) {
val showOnboarding = !preferencesStorage.wasTwinsOnboardingShown()
if (showOnboarding) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
return
}
}
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
companion object {
private const val CARD_SHOP_URI =
val handler = homeMiddleware
const val CARD_SHOP_URI =
"https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate"
}
}
private val homeMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
when (action) {
is HomeAction.Init -> {
store.dispatch(GlobalAction.RestoreAppCurrency)
store.dispatch(GlobalAction.GetMoonPayUserStatus)
store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted()))
}
is HomeAction.ReadCard -> {
if (!preferencesStorage.wasDisclaimerAccepted()) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
} else {
launchOnboardingService()
}
}
is HomeAction.GoToShop -> store.dispatch(HomeAction.SetOpenUrl(HomeMiddleware.CARD_SHOP_URI))
}
next(action)
}
}
}
private fun launchOnboardingService() {
fun changeButtonState(state: ButtonState) {
val btnState = IndeterminateProgressButton(state)
store.dispatch(HomeAction.ChangeScanCardButtonState(btnState))
}
changeButtonState(ButtonState.PROGRESS)
val walletManager = store.state.globalState.tapWalletManager
val service = OnboardingService(
walletManager,
preferencesStorage.usedCardsPrefStorage
)
service.onReadyToProceed = {
store.dispatch(OnboardingAction.SetData(it))
changeButtonState(ButtonState.ENABLED)
store.dispatch(NavigationAction.NavigateTo(it.onboardingScreen))
}
service.onFailedToProceedToOnboardingCase = {
changeButtonState(ButtonState.ENABLED)
walletManager.onCardScanned(it)
store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet))
}
service.onError = {
changeButtonState(ButtonState.ENABLED)
store.dispatch(GlobalAction.DebugShowError(TapError.CustomError(it)))
}
store.dispatch(GlobalAction.ReadCard(service::onSuccess, {
changeButtonState(ButtonState.ENABLED)
store.dispatch(GlobalAction.DebugShowError(TapError.ScanCardError))
}))
}

View file

@ -13,18 +13,27 @@ private fun internalReduce(action: Action, state: AppState): HomeState {
if (action !is HomeAction) return state.homeState
var homeState = state.homeState
var state = state.homeState
when (action) {
is HomeAction.CheckIfFirstLaunch.Result -> {
homeState = homeState.copy(firstLaunch = action.firstLaunch)
is HomeAction.SetFragmentShareTransition -> {
state = state.copy(shareTransition = action.shareTransition)
}
is HomeAction.SetTermsOfUseState -> {
state = state.copy(isDisclaimerAccepted = action.isDisclaimerAccepted)
}
is HomeAction.SetOpenUrl -> {
state = state.copy(openUrl = action.url)
}
is HomeAction.ChangeScanCardButtonState -> {
state = state.copy(btnScanState = action.state)
}
is HomeAction.ShowDialog.ScanFails -> {
homeState = homeState.copy(dialog = HomeDialog.ScanFailsDialog)
state = state.copy(dialog = HomeDialog.ScanFailsDialog)
}
is HomeAction.HideDialog -> {
homeState = homeState.copy(dialog = null)
state = state.copy(dialog = null)
}
}
return homeState
return state
}

View file

@ -1,13 +1,19 @@
package com.tangem.tap.features.home.redux
import com.tangem.tap.common.entities.IndeterminateProgressButton
import com.tangem.tap.common.redux.global.StateDialog
import com.tangem.tap.common.redux.navigation.FragmentShareTransition
import com.tangem.tap.features.send.redux.states.ButtonState
import org.rekotlin.StateType
data class HomeState(
val firstLaunch: Boolean = true,
val dialog: StateDialog? = null
val openUrl: String? = null,
val isDisclaimerAccepted: Boolean = false,
val shareTransition: FragmentShareTransition? = null,
val btnScanState: IndeterminateProgressButton = IndeterminateProgressButton(ButtonState.ENABLED),
val dialog: StateDialog? = null,
) : StateType
sealed class HomeDialog: StateDialog {
object ScanFailsDialog: HomeDialog()
sealed class HomeDialog : StateDialog {
object ScanFailsDialog : HomeDialog()
}

View file

@ -0,0 +1,168 @@
package com.tangem.tap.features.onboarding
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.common.card.Card
import com.tangem.common.extensions.guard
import com.tangem.common.services.Result
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.domain.TapWalletManager
import com.tangem.tap.domain.TapWorkarounds.isTangemNote
import com.tangem.tap.domain.TapWorkarounds.isTangemWallet
import com.tangem.tap.domain.UrlBitmapLoader
import com.tangem.tap.domain.extensions.getArtworkUrl
import com.tangem.tap.domain.extensions.hasWallets
import com.tangem.tap.domain.extensions.makePrimaryWalletManager
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.domain.twins.isTangemTwin
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
import com.tangem.tap.features.onboarding.redux.OnboardingData
import com.tangem.tap.features.wallet.redux.Artwork
import com.tangem.tap.persistence.UsedCardsPrefStorage
import com.tangem.tap.store
import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
class OnboardingService(
private val tapWalletManager: TapWalletManager,
private val cardInfoStorage: UsedCardsPrefStorage
) {
var onReadyToProceed: ((OnboardingData) -> Unit)? = null
set(value) {
field = value
onboardingDataLinker.onReady = { value?.invoke(it) }
}
var onFailedToProceedToOnboardingCase: (suspend (ScanNoteResponse) -> Unit)? = null
var onError: ((String) -> Unit)? = null
private val onboardingDataLinker = OnboardingDataLinker()
suspend fun onSuccess(response: ScanNoteResponse) {
onboardingDataLinker.scanNoteResponse = response
val card = response.card
val hasWallets = card.hasWallets()
if (!card.isTangemNote() && !card.isTangemWallet() && !card.isTangemTwin()) {
if (hasWallets) {
cardInfoStorage.activated(card.cardId)
onFailedToProceedToOnboardingCase?.invoke(response)
return
} else {
cardInfoStorage.activated(card.cardId) // вызвать только после того, как был создан кошелек
onboardingDataLinker.navigateTo = AppScreen.OnboardingOther
}
}
if (cardInfoStorage.wasActivated(card.cardId)) {
onFailedToProceedToOnboardingCase?.invoke(response)
return
}
loadCardImage(card) { onboardingDataLinker.cardArtwork = it }
if (hasWallets) {
val walletManagerFactory = tapWalletManager.walletManagerFactory
when {
card.isTangemNote() -> loadBalanceNote(walletManagerFactory, response)
card.isTangemWallet() -> loadBalanceWallet(walletManagerFactory, response)
card.isTangemTwin() -> loadBalanceTwins(walletManagerFactory, response)
}
} else {
onboardingDataLinker.valueOfWalletAmount = BigDecimal.ZERO
}
onboardingDataLinker.navigateTo = when {
card.isTangemNote() -> AppScreen.OnboardingNote
card.isTangemWallet() -> AppScreen.OnboardingWallet
card.isTangemTwin() -> AppScreen.OnboardingTwins
else -> AppScreen.OnboardingOther
}
}
private suspend fun loadBalanceNote(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) {
val walletManager: WalletManager = walletManagerFactory.makePrimaryWalletManager(response).guard {
onError?.invoke("Can't proceed: primary wallet manager didn't create")
return
}
walletManager.update()
store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
val wallet = walletManager.wallet
val valueOfAmount = wallet.amounts[AmountType.Coin]?.value
if (valueOfAmount == null) {
onError?.invoke("Amount can't be NULL")
return
}
onboardingDataLinker.valueOfWalletAmount = valueOfAmount
}
private suspend fun loadBalanceWallet(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) {
}
private suspend fun loadBalanceTwins(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) {
}
private suspend fun loadCardImage(card: Card, callback: (Artwork?) -> Unit) {
fun loadBitmap(url: String, callback: (Artwork?) -> Unit) {
UrlBitmapLoader().loadBitmap(url) {
when (it) {
is Result.Success -> callback(Artwork(url, it.data))
is Result.Failure -> callback(null)
}
}
}
when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) {
is Result.Success -> {
val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL
loadBitmap(artworkUrl, callback)
}
is Result.Failure -> loadBitmap(Artwork.DEFAULT_IMG_URL, callback)
}
}
}
class OnboardingDataLinker {
var onReady: ((OnboardingData) -> Unit)? = null
internal var scanNoteResponse: ScanNoteResponse? = null
set(value) {
field = value
tryToProceed()
}
internal var cardArtwork: Artwork? = null
set(value) {
field = value
tryToProceed()
}
internal var valueOfWalletAmount: BigDecimal? = null
set(value) {
field = value
tryToProceed()
}
internal var navigateTo: AppScreen? = null
set(value) {
field = value
tryToProceed()
}
private fun tryToProceed() {
if (!isReady()) return
val data = OnboardingData(scanNoteResponse!!, cardArtwork!!, valueOfWalletAmount, navigateTo!!)
onReady?.invoke(data)
}
private fun isReady(): Boolean {
val objList = mutableListOf(scanNoteResponse, cardArtwork, valueOfWalletAmount, navigateTo)
return !objList.any { it == null }
}
}

View file

@ -0,0 +1,133 @@
package com.tangem.tap.features.onboarding.products.note
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import androidx.transition.TransitionInflater
import com.tangem.tap.common.extensions.addOnBackPressedDispatcher
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
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.redux.OnboardingAction
import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep
import com.tangem.tap.features.send.BaseStoreFragment
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_onboarding.*
import org.rekotlin.StoreSubscriber
/**
[REDACTED_AUTHOR]
*/
class OnboardingNoteFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber<OnboardingNoteState> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.addOnBackPressedDispatcher {
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingNoteStep.None))
}
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(R.transition.fade)
exitTransition = inflater.inflateTransition(R.transition.fade)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
store.dispatch(OnboardingNoteAction.Init)
imv_front_card.setImageBitmap(store.state.onboardingState.onboardingData?.cardArtwork?.artwork)
}
override fun subscribeToStore() {
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.onboardingNoteState == newState.onboardingNoteState
}.select { it.onboardingNoteState }
}
}
override fun newState(state: OnboardingNoteState) {
if (activity == null) return
pb_state.max = state.steps.size - 1
onboarding_main_container.beginDelayedTransition()
when (state.currentStep) {
OnboardingNoteStep.None -> setupNoneState(state)
OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state)
OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state)
OnboardingNoteStep.Done -> setupSuccessState(state)
}
}
private fun setupNoneState(state: OnboardingNoteState) {
pb_state.progress = state.progress
btn_main_action.isVisible = true
btn_alternative_action.isVisible = true
btn_check_balance.isVisible = true
tv_header.isVisible = true
tv_body.isVisible = true
btn_main_action.text = "NULL"
btn_alternative_action.text = "NULL"
btn_check_balance.text = "NULL balance"
tv_header.text = "NULL"
tv_body.text = "NULL"
}
private fun setupCreateWalletState(state: OnboardingNoteState) {
pb_state.progress = state.progress
btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet)
btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) }
btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean)
btn_alternative_action.setOnClickListener { }
btn_check_balance.isVisible = false
btn_check_balance.setOnClickListener { }
tv_header.setText(R.string.onboarding_create_wallet_header)
tv_body.setText(R.string.onboarding_create_wallet_body)
}
private fun setupTopUpWalletState(state: OnboardingNoteState) {
pb_state.progress = state.progress
btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto)
btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.TopUp) }
btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address)
btn_alternative_action.setOnClickListener { }
btn_check_balance.isVisible = false
btn_check_balance.setOnClickListener { }
tv_header.setText(R.string.onboarding_top_up_header)
tv_body.setText(R.string.onboarding_top_up_body)
}
private fun setupSuccessState(state: OnboardingNoteState) {
pb_state.progress = state.progress
btn_main_action.setText(R.string.onboarding_done_button_continue)
btn_main_action.setOnClickListener { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) }
btn_alternative_action.isVisible = false
btn_alternative_action.setText("")
btn_alternative_action.setOnClickListener { }
btn_check_balance.isVisible = false
btn_check_balance.setOnClickListener { }
tv_header.setText(R.string.onboarding_done_header)
tv_body.setText(R.string.onboarding_done_body)
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.blockchain.common.WalletManager
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
sealed class OnboardingNoteAction : Action {
object Init : OnboardingNoteAction()
object CreateWallet : OnboardingNoteAction()
object TopUp : OnboardingNoteAction()
object Done : OnboardingNoteAction()
data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction()
}

View file

@ -0,0 +1,72 @@
package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.common.CompletionResult
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.onboarding.redux.OnboardingAction
import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
class OnboardingNoteMiddleware {
companion object {
private const val CARD_SHOP_URI =
"https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate"
val handler = onboardingNoteMiddleware
}
}
private val onboardingNoteMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
handleMainOnboardingAction(action, ::handleNoteAction)
next(action)
}
}
}
private fun handleMainOnboardingAction(action: Action, isNotMainAction: (Action) -> Unit) {
val action = action as? OnboardingAction ?: isNotMainAction(action)
when (action) {
}
}
private fun handleNoteAction(action: Action) {
val onboardingState = store.state.onboardingState
val noteState = store.state.onboardingNoteState
when (action) {
is OnboardingNoteAction.SetWalletManager -> {
}
is OnboardingNoteAction.Init -> {
val step = if (noteState.walletManager == null) {
OnboardingNoteStep.CreateWallet
} else {
OnboardingNoteStep.TopUpWallet
}
store.dispatch(OnboardingAction.SetInitialStepOfScreen(step))
}
is OnboardingNoteAction.CreateWallet -> {
scope.launch {
val scanNoteResponse = onboardingState.onboardingData?.scanNoteResponse
val result = tangemSdkManager.createWallet(scanNoteResponse?.card?.cardId)
when (result) {
is CompletionResult.Success -> {
// val scanNoteResponse = scanNoteResponse!!.copy(card = result.data)
// store.dispatch(OnboardingAction.SetScanNoteResponse(scanNoteResponse))
}
is CompletionResult.Failure -> {
store.dispatch(OnboardingAction.Error("Failed to create wallet"))
}
}
}
}
is OnboardingNoteAction.TopUp -> {
}
}
}

View file

@ -0,0 +1,38 @@
package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.onboarding.redux.OnboardingAction
import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep
import org.rekotlin.Action
class OnboardingNoteReducer {
companion object {
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 OnboardingAction.SetInitialStepOfScreen -> {
(action.step as? OnboardingNoteStep)?.let {
if (it != state.currentStep && state.steps.contains(it)) {
state = state.copy(currentStep = it)
}
}
}
OnboardingAction.SwitchToNextStep -> {
val progress = state.progress
if (progress >= 0 && progress != state.steps.size - 1) {
state = state.copy(currentStep = state.steps[progress + 1])
}
}
is OnboardingNoteAction.SetWalletManager -> {
state = state.copy(walletManager = action.walletManager)
}
}
return state
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.blockchain.common.WalletManager
import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep
import org.rekotlin.StateType
/**
[REDACTED_AUTHOR]
*/
data class OnboardingNoteState(
val walletManager: WalletManager? = null,
val currentStep: OnboardingNoteStep = OnboardingNoteStep.None,
val steps: List<OnboardingNoteStep> = OnboardingNoteStep.values().toList(),
) : StateType {
val progress: Int
get() = steps.indexOf(currentStep)
}

View file

@ -0,0 +1,50 @@
package com.tangem.tap.features.onboarding.products.old
import android.os.Bundle
import android.view.View
import androidx.transition.TransitionInflater
import com.tangem.tap.common.extensions.addOnBackPressedDispatcher
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.onboarding.redux.OnboardingAction
import com.tangem.tap.features.onboarding.redux.OnboardingOtherCardsState
import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep
import com.tangem.tap.features.send.BaseStoreFragment
import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
/**
[REDACTED_AUTHOR]
*/
class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber<OnboardingOtherCardsState> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.addOnBackPressedDispatcher {
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done))
}
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(R.transition.fade)
exitTransition = inflater.inflateTransition(R.transition.fade)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun subscribeToStore() {
// store.subscribe(this) { state ->
// state.skipRepeats { oldState, newState ->
// oldState.onboardingOtherState == newState.onboardingOtherState
// }.select { it.onboardingOtherState }
// }
}
override fun newState(state: OnboardingOtherCardsState) {
if (activity == null) return
}
}

View file

@ -0,0 +1,52 @@
package com.tangem.tap.features.onboarding.products.wallet
import android.os.Bundle
import android.view.View
import androidx.transition.TransitionInflater
import com.tangem.tap.common.extensions.addOnBackPressedDispatcher
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
import com.tangem.tap.features.onboarding.redux.OnboardingAction
import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep
import com.tangem.tap.features.send.BaseStoreFragment
import com.tangem.tap.store
import com.tangem.wallet.R
import org.rekotlin.StoreSubscriber
/**
[REDACTED_AUTHOR]
*/
class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_wallet), StoreSubscriber<OnboardingWalletState> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.addOnBackPressedDispatcher {
store.dispatch(NavigationAction.PopBackTo())
store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done))
}
val inflater = TransitionInflater.from(requireContext())
enterTransition = inflater.inflateTransition(R.transition.fade)
exitTransition = inflater.inflateTransition(R.transition.fade)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
store.dispatch(OnboardingAction.OnboardingOtherAction.Init)
}
override fun subscribeToStore() {
store.subscribe(this) { state ->
state.skipRepeats { oldState, newState ->
oldState.onboardingWalletState == newState.onboardingWalletState
}.select { it.onboardingWalletState }
}
}
override fun newState(state: OnboardingWalletState) {
if (activity == null) return
}
}

View file

@ -0,0 +1,19 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
sealed class OnboardingWalletAction : Action {
object Init : OnboardingWalletAction()
object CreateWallet : OnboardingWalletAction()
object TopUp : OnboardingWalletAction()
object Done : OnboardingWalletAction()
object ProceedBackup : OnboardingWalletAction()
sealed class Backup : Action {
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Middleware
class OnboardingWalletMiddleware {
companion object {
val handler = onboardingWalletMiddleware
}
}
private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
next(action)
}
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
class OnboardingWalletReducer {
companion object {
fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state)
}
}
private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState {
var state = appState.onboardingWalletState
when (action) {
}
return state
}

View file

@ -0,0 +1,15 @@
package com.tangem.tap.features.onboarding.products.wallet.redux
import com.tangem.tap.features.onboarding.redux.OnboardingStep
import org.rekotlin.StateType
/**
[REDACTED_AUTHOR]
*/
data class OnboardingWalletState(
val any: String? = null
) : StateType
enum class OnboardingWalletStep : OnboardingStep {
None, CreateWallet, TopUpWallet, Backup, Done
}

View file

@ -0,0 +1,22 @@
package com.tangem.tap.features.onboarding.redux
import org.rekotlin.Action
/**
[REDACTED_AUTHOR]
*/
sealed class OnboardingAction : Action {
data class SetInitialStepOfScreen(val step: OnboardingStep) : OnboardingAction()
object SwitchToNextStep : OnboardingAction()
data class SetData(val data: OnboardingData) : OnboardingAction()
sealed class OnboardingOtherAction : Action {
object Init : OnboardingOtherAction()
object CreateWallet : OnboardingOtherAction()
object Done : OnboardingOtherAction()
}
class Error(val message: String) : OnboardingAction()
}

View file

@ -0,0 +1,22 @@
package com.tangem.tap.features.onboarding.redux
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.store
import org.rekotlin.Middleware
class OnboardingMiddleware {
companion object {
val handler = onboardingMiddleware
}
}
private val onboardingMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
val onboardingState = store.state.onboardingState
when (action) {
}
next(action)
}
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.tap.features.onboarding.redux
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
class OnboardingReducer {
companion object {
fun reduce(action: Action, state: AppState): OnboardingState = internalReduce(action, state)
}
}
private fun internalReduce(action: Action, appState: AppState): OnboardingState {
if (action !is OnboardingAction) return appState.onboardingState
var state = appState.onboardingState
when (action) {
is OnboardingAction.SetData -> {
state = state.copy(onboardingData = action.data)
}
}
return state
}

View file

@ -0,0 +1,11 @@
package com.tangem.tap.features.onboarding.redux
import org.rekotlin.StateType
data class OnboardingState(
val onboardingData: OnboardingData? = null,
) : StateType
data class OnboardingOtherCardsState(
val any: String? = null
) : StateType

View file

@ -0,0 +1,26 @@
package com.tangem.tap.features.onboarding.redux
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.domain.tasks.ScanNoteResponse
import com.tangem.tap.features.wallet.redux.Artwork
import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
data class OnboardingData(
val scanNoteResponse: ScanNoteResponse,
val cardArtwork: Artwork,
val balance: BigDecimal?,
val onboardingScreen: AppScreen
)
interface OnboardingStep
enum class OnboardingNoteStep : OnboardingStep {
None, CreateWallet, TopUpWallet, Done
}
enum class OnboardingOtherStep : OnboardingStep {
CreateWallet, Done
}

View file

@ -16,7 +16,7 @@ import com.tangem.wallet.R
import kotlinx.android.synthetic.main.fragment_twin_cards.*
import kotlinx.android.synthetic.main.layout_twin_cards.*
class TwinsOnboardingFragment : Fragment(R.layout.fragment_twin_cards) {
class OnboardingTwinsFragment : Fragment(R.layout.fragment_twin_cards) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View file

@ -107,7 +107,7 @@ class SingleWalletView : WalletView {
tv_twin_card_number.hide()
}
if (twinCardsState?.showTwinOnboarding == true) {
store.dispatch(NavigationAction.NavigateTo(AppScreen.TwinsOnboarding))
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
}