Updated on 2026-08-14
This commit is contained in:
commit
2541461d44
954 changed files with 22834 additions and 21200 deletions
|
|
@ -109,6 +109,8 @@ dependencies {
|
|||
implementation(projects.domain.promo)
|
||||
implementation(projects.domain.promo.models)
|
||||
implementation(projects.domain.networks)
|
||||
implementation(projects.domain.quotes)
|
||||
implementation(projects.domain.notifications)
|
||||
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -152,9 +154,10 @@ dependencies {
|
|||
implementation(projects.data.nft)
|
||||
implementation(projects.data.onramp)
|
||||
implementation(projects.data.networks)
|
||||
implementation(projects.data.quotes)
|
||||
implementation(projects.data.blockaid)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.onboarding)
|
||||
implementation(projects.features.referral.impl)
|
||||
implementation(projects.features.referral.domain)
|
||||
implementation(projects.features.referral.data)
|
||||
|
|
@ -273,6 +276,7 @@ dependencies {
|
|||
implementation(deps.reKotlin)
|
||||
implementation(deps.zxing.qrCore)
|
||||
implementation(deps.coil)
|
||||
implementation(deps.coil.gif)
|
||||
implementation(deps.amplitude)
|
||||
implementation(deps.kotsonGson)
|
||||
implementation(deps.spongecastle.core)
|
||||
|
|
@ -318,6 +322,7 @@ dependencies {
|
|||
/** Chucker */
|
||||
debugImplementation(deps.chucker)
|
||||
mockedImplementation(deps.chuckerStub)
|
||||
externalImplementation(deps.chuckerStub)
|
||||
internalImplementation(deps.chuckerStub)
|
||||
releaseImplementation(deps.chuckerStub)
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@
|
|||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="onramp-success"
|
||||
android:host="*"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 29f73d909e14cb3760118b197b1929a6088bc40e
|
||||
Subproject commit a20134ee4be4d7e1e34c6f44089b3cb0713eacc3
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.utils.Provider
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.WeakHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class DecomposeFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val component by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val tag = requireArguments().getString(TAG_KEY)
|
||||
val builder = componentsBuilders[tag]
|
||||
|
||||
requireNotNull(builder?.build()) {
|
||||
"Component builder is not set, call newInstance() for DecomposeFragment creation first."
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
component.Content(modifier)
|
||||
}
|
||||
|
||||
private class ComponentBuilder<C : ComposableContentComponent, P : Any, F : ComponentFactory<P, C>>(
|
||||
private val contextProvider: Provider<AppComponentContext>,
|
||||
private val params: P,
|
||||
private val componentFactory: F,
|
||||
) {
|
||||
|
||||
fun build(): C = componentFactory.create(contextProvider(), params)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG_KEY = "tag"
|
||||
|
||||
private val componentsBuilders = WeakHashMap<String, ComponentBuilder<*, *, *>>()
|
||||
|
||||
fun <C : ComposableContentComponent, P : Any, F : ComponentFactory<P, C>> newInstance(
|
||||
tag: String,
|
||||
contextProvider: Provider<AppComponentContext>,
|
||||
params: P,
|
||||
componentFactory: F,
|
||||
): Fragment {
|
||||
this@Companion.componentsBuilders[tag] = ComponentBuilder(contextProvider, params, componentFactory)
|
||||
|
||||
return DecomposeFragment().apply {
|
||||
this.arguments = bundleOf(TAG_KEY to tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,6 @@ import androidx.activity.SystemBarStyle
|
|||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode
|
||||
|
|
@ -20,19 +19,11 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import arrow.core.getOrElse
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.arkivanov.decompose.value.subscribe
|
||||
import com.arkivanov.essenty.lifecycle.asEssentyLifecycle
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
|
|
@ -41,12 +32,6 @@ import com.tangem.core.decompose.di.RootAppComponentContext
|
|||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.core.navigation.email.EmailSender
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.message.EventMessageEffect
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.data.balancehiding.DefaultDeviceFlipDetector
|
||||
import com.tangem.data.card.sdk.CardSdkOwner
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
|
|
@ -62,37 +47,28 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
|
|||
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.sdk.extensions.init
|
||||
import com.tangem.tap.common.ActivityResultCallbackHolder
|
||||
import com.tangem.tap.common.DialogManager
|
||||
import com.tangem.tap.common.OnActivityResultCallback
|
||||
import com.tangem.tap.common.SnackbarHandler
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.showFragmentAllowingStateLoss
|
||||
import com.tangem.tap.common.redux.NotificationsHandler
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.features.main.MainViewModel
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphAction
|
||||
import com.tangem.tap.routing.component.RoutingComponent
|
||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||
import com.tangem.tap.routing.toggle.RoutingFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ActivityMainBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -105,7 +81,6 @@ lateinit var tangemSdkManager: TangemSdkManager
|
|||
lateinit var backupService: BackupService
|
||||
internal var lockUserWalletsTimer: LockUserWalletsTimer? = null
|
||||
private set
|
||||
var notificationsHandler: NotificationsHandler? = null
|
||||
|
||||
private val coroutineContext: CoroutineContext
|
||||
get() = SupervisorJob() + Dispatchers.IO + FeatureCoroutineExceptionHandler.create("scope")
|
||||
|
|
@ -117,7 +92,7 @@ val mainScope = CoroutineScope(mainCoroutineContext)
|
|||
|
||||
@Suppress("LargeClass")
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder {
|
||||
class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||
|
||||
@Inject
|
||||
lateinit var appStateHolder: AppStateHolder
|
||||
|
|
@ -181,9 +156,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var backupServiceHolder: BackupServiceHolder
|
||||
|
||||
@Inject
|
||||
lateinit var onboardingV2FeatureToggles: OnboardingV2FeatureToggles
|
||||
|
||||
@Inject
|
||||
lateinit var setGoogleServicesAvailabilityUseCase: SetGoogleServicesAvailabilityUseCase
|
||||
|
||||
|
|
@ -193,9 +165,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var dispatchers: CoroutineDispatcherProvider
|
||||
|
||||
@Inject
|
||||
internal lateinit var routingFeatureToggles: RoutingFeatureToggles
|
||||
|
||||
@Inject
|
||||
internal lateinit var uiDependencies: UiDependencies
|
||||
|
||||
|
|
@ -209,9 +178,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
// TODO: fixme: inject through DI
|
||||
private val intentProcessor: IntentProcessor = IntentProcessor()
|
||||
|
||||
private var snackbar: Snackbar? = null
|
||||
private val dialogManager = DialogManager()
|
||||
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
|
||||
|
||||
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
||||
|
||||
|
|
@ -248,13 +215,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
installActivityDependencies()
|
||||
observeAppThemeModeUpdates()
|
||||
|
||||
if (routingFeatureToggles.isNavigationRefactoringEnabled) {
|
||||
setRootContent()
|
||||
} else {
|
||||
setContentView(R.layout.activity_main)
|
||||
installRouting()
|
||||
installEventMessageEffect()
|
||||
}
|
||||
setRootContent()
|
||||
|
||||
initContent()
|
||||
|
||||
|
|
@ -271,33 +232,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
lifecycle.addObserver(defaultDeviceFlipDetector)
|
||||
}
|
||||
|
||||
private fun installEventMessageEffect() {
|
||||
binding.composeView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
binding.composeView.setContent {
|
||||
TangemTheme(
|
||||
activity = this,
|
||||
uiDependencies = uiDependencies,
|
||||
overrideSystemBarColors = false,
|
||||
) {
|
||||
EventMessageEffect(
|
||||
onShowSnackbar = { message, _ ->
|
||||
showSnackbar(
|
||||
text = message.message.resolveReference(resources),
|
||||
length = when (message.duration) {
|
||||
SnackbarMessage.Duration.Short -> Snackbar.LENGTH_SHORT
|
||||
SnackbarMessage.Duration.Long -> Snackbar.LENGTH_LONG
|
||||
SnackbarMessage.Duration.Indefinite -> Snackbar.LENGTH_INDEFINITE
|
||||
},
|
||||
buttonTitle = message.actionLabel?.resolveReference(resources),
|
||||
action = message.action,
|
||||
onDismiss = message.onDismissRequest,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRootContent() {
|
||||
// for now activity is singleTop and after going to ChromeCustomTab it calls onCreate but onDestroy
|
||||
// doesn't calls. It lead to issue that decompose nav stack is not saved in bundle and to restore it
|
||||
|
|
@ -312,64 +246,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
}
|
||||
|
||||
private fun installRouting() {
|
||||
// for now activity is singleTop and after going to ChromeCustomTab it calls onCreate but onDestroy
|
||||
// doesn't calls. It lead to issue that decompose nav stack is not saved in bundle and to restore it
|
||||
// we try to init component with previous stack
|
||||
val routingComponent = routingComponentFactory.create(
|
||||
context = rootComponentContext,
|
||||
initialStack = appRouterConfig.stack,
|
||||
)
|
||||
|
||||
appRouterConfig.routerScope = mainScope
|
||||
appRouterConfig.componentRouter = routingComponent.router
|
||||
appRouterConfig.snackbarHandler = this
|
||||
|
||||
routingComponent.stack.subscribe(lifecycle.asEssentyLifecycle()) { childStack ->
|
||||
val stack = childStack.backStack
|
||||
.plus(childStack.active)
|
||||
.map { it.configuration }
|
||||
|
||||
if (stack == appRouterConfig.stack) return@subscribe
|
||||
|
||||
appRouterConfig.stack = stack
|
||||
|
||||
if (stack.size == 1) {
|
||||
supportFragmentManager.run {
|
||||
val activeChildName = stack.first().path
|
||||
|
||||
(0 until backStackEntryCount)
|
||||
.mapNotNull { getBackStackEntryAt(it).name }
|
||||
.filter { it != activeChildName }
|
||||
.forEach {
|
||||
popBackStackImmediate(it, POP_BACK_STACK_INCLUSIVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (val child = childStack.active.instance) {
|
||||
is RoutingComponent.Child.Initial -> Unit
|
||||
is RoutingComponent.Child.LegacyFragment -> {
|
||||
supportFragmentManager.showFragmentAllowingStateLoss(child.name, child.fragmentProvider)
|
||||
}
|
||||
is RoutingComponent.Child.LegacyIntent -> {
|
||||
startActivity(child.intent)
|
||||
}
|
||||
is RoutingComponent.Child.ComposableComponent -> error("Unsupported child: $child")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun installActivityDependencies() {
|
||||
cardSdkOwner.register(activity = this)
|
||||
tangemSdkManager = injectedTangemSdkManager
|
||||
|
||||
if (onboardingV2FeatureToggles.isOnboardingV2Enabled) {
|
||||
backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this)
|
||||
backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually
|
||||
} else {
|
||||
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
|
||||
}
|
||||
backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this)
|
||||
backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually
|
||||
|
||||
lockUserWalletsTimer = LockUserWalletsTimer(
|
||||
context = this,
|
||||
|
|
@ -439,17 +321,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (!routingFeatureToggles.isNavigationRefactoringEnabled) {
|
||||
// TODO: RESEARCH! NotificationsHandler is created in onResume and destroyed in onStop
|
||||
notificationsHandler = NotificationsHandler(binding.fragmentContainer)
|
||||
}
|
||||
|
||||
navigateToInitialScreenIfNeeded(intent)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
notificationsHandler = null
|
||||
dialogManager.onStop()
|
||||
super.onStop()
|
||||
}
|
||||
|
|
@ -482,31 +357,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
MutableAppThemeModeHolder.isDarkThemeActive = isDarkTheme()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
/*
|
||||
* We need to manually change the background color of the activity when the UI mode changes to prevent
|
||||
* flickering when navigating between fragments.
|
||||
*
|
||||
|
||||
* `android:configChanges="uiMode"` is set in the manifest.
|
||||
* */
|
||||
if (routingFeatureToggles.isNavigationRefactoringEnabled.not()) {
|
||||
updateAppBackground()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAppBackground() {
|
||||
val backgroundColor = if (isDarkTheme()) {
|
||||
TangemColorPalette.Dark6
|
||||
} else {
|
||||
TangemColorPalette.White
|
||||
}
|
||||
|
||||
findViewById<CoordinatorLayout>(R.id.fragment_container).setBackgroundColor(backgroundColor.toArgb())
|
||||
}
|
||||
|
||||
private fun isDarkTheme(): Boolean {
|
||||
return when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
|
||||
Configuration.UI_MODE_NIGHT_YES -> true
|
||||
|
|
@ -528,29 +378,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
}
|
||||
|
||||
override fun showSnackbar(@StringRes text: Int, length: Int, @StringRes buttonTitle: Int?, action: (() -> Unit)?) {
|
||||
showSnackbar(
|
||||
getString(text),
|
||||
length,
|
||||
buttonTitle?.let(::getString),
|
||||
action = { action?.invoke() },
|
||||
)
|
||||
}
|
||||
|
||||
override fun showSnackbar(text: TextReference, length: Int, buttonTitle: TextReference?, action: (() -> Unit)?) {
|
||||
showSnackbar(
|
||||
text = text.resolveReference(resources),
|
||||
length = length,
|
||||
buttonTitle = buttonTitle?.resolveReference(resources),
|
||||
action = { action?.invoke() },
|
||||
)
|
||||
}
|
||||
|
||||
override fun dismissSnackbar() {
|
||||
snackbar?.dismiss()
|
||||
snackbar = null
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
|
|
@ -578,40 +405,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
return if (result) super.dispatchTouchEvent(event) else false
|
||||
}
|
||||
|
||||
private fun showSnackbar(
|
||||
text: String,
|
||||
length: Int,
|
||||
buttonTitle: String?,
|
||||
action: (() -> Unit)? = null,
|
||||
onDismiss: () -> Unit = {},
|
||||
) {
|
||||
if (snackbar != null) return
|
||||
|
||||
snackbar = Snackbar.make(binding.fragmentContainer, text, length).apply {
|
||||
val textColor = getColor(R.color.text_primary_2)
|
||||
|
||||
setBackgroundTint(getColor(R.color.button_primary))
|
||||
setActionTextColor(textColor)
|
||||
setTextColor(textColor)
|
||||
|
||||
if (buttonTitle != null && action != null) {
|
||||
setAction(buttonTitle, { action() })
|
||||
}
|
||||
|
||||
addCallback(
|
||||
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
|
||||
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||
onDismiss()
|
||||
snackbar = null
|
||||
removeCallback(this)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
snackbar?.show()
|
||||
}
|
||||
|
||||
private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) {
|
||||
val backStack = appRouterConfig.stack ?: emptyList()
|
||||
// TODO move inital navigation to navigation component ([REDACTED_JIRA])
|
||||
|
|
@ -662,7 +455,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
}
|
||||
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
viewModel.checkForUnfinishedBackup()
|
||||
}
|
||||
|
||||
private fun observePolkadotAccountHealthCheck() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.app.Application
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.ThreadPolicy
|
||||
import android.os.StrictMode.VmPolicy
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import coil.ImageLoader
|
||||
|
|
@ -71,9 +74,7 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.EntryPoints
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.*
|
||||
import org.rekotlin.Store
|
||||
import kotlin.collections.set
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
|
||||
|
|
@ -228,12 +229,32 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
|
||||
// endregion
|
||||
|
||||
private val appScope = MainScope()
|
||||
|
||||
override fun onCreate() {
|
||||
enableStrictModeInDebug()
|
||||
super.onCreate()
|
||||
|
||||
init()
|
||||
}
|
||||
|
||||
updateLogFiles()
|
||||
private fun enableStrictModeInDebug() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
StrictMode.setThreadPolicy(
|
||||
ThreadPolicy.Builder()
|
||||
.detectDiskReads()
|
||||
.detectDiskWrites()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
StrictMode.setVmPolicy(
|
||||
VmPolicy.Builder()
|
||||
.detectLeakedSqlLiteObjects()
|
||||
.detectLeakedClosableObjects()
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLogFiles() {
|
||||
|
|
@ -260,18 +281,28 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
foregroundActivityObserver = ForegroundActivityObserver()
|
||||
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
|
||||
|
||||
// TODO: Try to performance and user experience.
|
||||
// [REDACTED_JIRA]
|
||||
// We need to initialize the toggles and excludedBlockchainsManager before the MainActivity starts using them.
|
||||
runBlocking {
|
||||
awaitAll(
|
||||
async { featureTogglesManager.init() },
|
||||
async { excludedBlockchainsManager.init() },
|
||||
async { initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) },
|
||||
async {
|
||||
featureTogglesManager.init()
|
||||
},
|
||||
async {
|
||||
excludedBlockchainsManager.init()
|
||||
},
|
||||
)
|
||||
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
||||
}
|
||||
|
||||
appScope.launch {
|
||||
launch(Dispatchers.IO) {
|
||||
loadNativeLibraries()
|
||||
updateLogFiles()
|
||||
}
|
||||
}
|
||||
|
||||
loadNativeLibraries()
|
||||
ExceptionHandler.append(blockchainExceptionHandler)
|
||||
|
||||
if (LogConfig.network.blockchainSdkNetwork) {
|
||||
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
||||
createNetworkLoggingInterceptor(),
|
||||
|
|
@ -287,9 +318,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory, Configurat
|
|||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
appStateHolder.mainStore = store
|
||||
|
||||
walletConnect2Repository.init(projectId = environmentConfigStorage.getConfigSync().walletConnectProjectId)
|
||||
appStateHolder.mainStore = store
|
||||
walletConnect2Repository.init(
|
||||
projectId = environmentConfigStorage.getConfigSync().walletConnectProjectId,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createReduxStore(): Store<AppState> {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ import com.tangem.tap.common.ui.*
|
|||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog
|
||||
import com.tangem.tap.features.details.ui.walletconnect.dialogs.*
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.products.twins.ui.dialog.TwinningProcessNotCompletedDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.ConfirmDiscardingBackupDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedBackupFoundDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.WalletActivationErrorDialog
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.WalletAlreadyWasUsedDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
|
@ -56,10 +58,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
messageRes = R.string.nfc_error_unavailable,
|
||||
context = context,
|
||||
)
|
||||
is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context)
|
||||
is AppDialog.TestActionsDialog -> TestActionsBottomSheetDialog(state.dialog, context)
|
||||
is OnboardingDialog.TwinningProcessNotCompleted -> TwinningProcessNotCompletedDialog.create(context)
|
||||
is OnboardingDialog.InterruptOnboarding -> InterruptOnboardingDialog.create(context, state.dialog)
|
||||
is OnboardingDialog.WalletActivationError -> WalletActivationErrorDialog.create(context, state.dialog)
|
||||
is WalletConnectDialog.UnsupportedCard ->
|
||||
SimpleAlertDialog.create(
|
||||
|
|
@ -143,9 +141,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
messageRes = R.string.unsupported_wc_version,
|
||||
context = context,
|
||||
)
|
||||
is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context)
|
||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||
is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(
|
||||
context = context,
|
||||
scanResponse = state.dialog.scanResponse,
|
||||
|
|
@ -154,10 +149,6 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
|||
context = context,
|
||||
unfinishedBackupScanResponse = state.dialog.scanResponse,
|
||||
)
|
||||
is BackupDialog.ResetBackupCard -> ResetBackupCardDialog.create(
|
||||
context = context,
|
||||
cardId = state.dialog.cardId,
|
||||
)
|
||||
is AppDialog.TokensAreLinkedDialog -> SimpleAlertDialog.create(
|
||||
title = context.getString(state.dialog.titleRes, state.dialog.currencySymbol),
|
||||
message = context.getString(
|
||||
|
|
|
|||
|
|
@ -1,16 +1,5 @@
|
|||
package com.tangem.tap.common
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -21,41 +10,4 @@ object TestActions {
|
|||
var testAmountInjectionForWalletManagerEnabled = false
|
||||
}
|
||||
|
||||
typealias TestAction = Pair<String, () -> Unit>
|
||||
|
||||
class TestActionsBottomSheetDialog(
|
||||
private val appDialog: AppDialog.TestActionsDialog,
|
||||
context: Context,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(createContentView())
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.peekHeight = 400
|
||||
setOnCancelListener {
|
||||
store.dispatchDialogHide()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createContentView(): View {
|
||||
val actionButtons = mutableListOf<View>()
|
||||
appDialog.actionsList.forEach { (actionName, action) ->
|
||||
val view = Button(context).apply {
|
||||
text = actionName
|
||||
setOnClickListener {
|
||||
action()
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
actionButtons.add(view)
|
||||
}
|
||||
|
||||
return LinearLayoutCompat(context).apply {
|
||||
orientation = LinearLayoutCompat.VERTICAL
|
||||
actionButtons.forEach { addView(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias TestAction = Pair<String, () -> Unit>
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import timber.log.Timber
|
||||
|
||||
fun FragmentManager.showFragmentAllowingStateLoss(name: String, fragmentProvider: Provider<Fragment>) {
|
||||
if (backStackEntryCount > 0) {
|
||||
val currentFragmentName = getBackStackEntryAt(backStackEntryCount - 1).name
|
||||
|
||||
if (name == currentFragmentName) {
|
||||
Timber.i("Fragment $name is already at the top of the stack")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Timber.i("Showing $name route")
|
||||
|
||||
val isPoppedBack = popBackStackImmediate(name, 0)
|
||||
|
||||
if (!isPoppedBack) {
|
||||
val fragment = fragmentProvider()
|
||||
|
||||
if (fragment is DialogFragment) {
|
||||
fragment.showDialog(fragmentManager = this, name)
|
||||
} else {
|
||||
fragment.showFragment(fragmentManager = this, name)
|
||||
}
|
||||
|
||||
Timber.i("Route $name is shown")
|
||||
} else {
|
||||
Timber.i("Route $name is found in backstack and shown")
|
||||
}
|
||||
}
|
||||
|
||||
private fun DialogFragment.showDialog(fragmentManager: FragmentManager, name: String) {
|
||||
val transaction = fragmentManager.beginTransaction()
|
||||
|
||||
try {
|
||||
transaction.addToBackStack(name)
|
||||
show(transaction, name)
|
||||
} catch (e: IllegalStateException) {
|
||||
transaction.add(this, name)
|
||||
transaction.addToBackStack(name)
|
||||
|
||||
transaction.commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Fragment.showFragment(fragmentManager: FragmentManager, name: String) {
|
||||
val transaction = fragmentManager.beginTransaction()
|
||||
|
||||
transaction.replace(R.id.fragment_container, this, name)
|
||||
transaction.addToBackStack(name)
|
||||
|
||||
transaction.commitAllowingStateLoss()
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -43,20 +45,16 @@ suspend fun Store<AppState>.onUserWalletSelected(userWallet: UserWallet) {
|
|||
state.globalState.tapWalletManager.onWalletSelected(userWallet)
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchErrorNotification(error: TapError) {
|
||||
dispatchOnMain(GlobalAction.ShowErrorNotification(error))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fatal used to indicate errors that should not normally occur
|
||||
*/
|
||||
fun Store<*>.dispatchDebugErrorNotification(message: String, fatal: Boolean = false) {
|
||||
fun Store<AppState>.dispatchDebugErrorNotification(message: String, fatal: Boolean = false) {
|
||||
val prefix = if (fatal) "FATAL ERROR: " else "DEBUG ERROR: "
|
||||
dispatchDebugErrorNotification(TapError.CustomError("$prefix $message"))
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchDebugErrorNotification(error: TapError) {
|
||||
dispatchOnMain(GlobalAction.DebugShowErrorNotification(error))
|
||||
fun Store<AppState>.dispatchDebugErrorNotification(error: TapError) {
|
||||
inject(DaggerGraphState::uiMessageSender).send(SnackbarMessage(stringReference(error.message ?: "debug error")))
|
||||
}
|
||||
|
||||
fun Store<*>.dispatchDialogShow(dialog: StateDialog) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.tap.common.images
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import coil.ImageLoader
|
||||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.memory.MemoryCache
|
||||
import coil.request.CachePolicy
|
||||
import coil.util.Logger
|
||||
|
|
@ -25,6 +28,15 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL
|
|||
.build()
|
||||
}
|
||||
}
|
||||
.components {
|
||||
// According to Coil ImageDecoder API is faster and supports animated WebP and HEIF
|
||||
// https://coil-kt.github.io/coil/gifs/
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
add(ImageDecoderDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
}
|
||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||
.memoryCache {
|
||||
MemoryCache.Builder(context)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ package com.tangem.tap.common.redux
|
|||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.TestAction
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.model.WalletAddressData
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
|
|
@ -18,15 +15,6 @@ sealed class AppDialog : StateDialog {
|
|||
val onOk: VoidCallback? = null,
|
||||
) : AppDialog()
|
||||
|
||||
internal data class AddressInfoDialog(
|
||||
val currency: Currency,
|
||||
val addressData: WalletAddressData,
|
||||
) : AppDialog()
|
||||
|
||||
data class TestActionsDialog(
|
||||
val actionsList: List<TestAction>,
|
||||
) : AppDialog()
|
||||
|
||||
data class RemoveWalletDialog(
|
||||
val currencyTitle: String,
|
||||
val onOk: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -4,11 +4,6 @@ import com.tangem.tap.common.redux.global.globalReducer
|
|||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectReducer
|
||||
import com.tangem.tap.features.home.redux.HomeReducer
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsReducer
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsReducer
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -20,14 +15,9 @@ fun appReducer(action: Action, state: AppState?): AppState {
|
|||
return AppState(
|
||||
globalState = globalReducer(action, state),
|
||||
homeState = HomeReducer.reduce(action, state),
|
||||
onboardingNoteState = OnboardingNoteReducer.reduce(action, state),
|
||||
onboardingWalletState = OnboardingWalletReducer.reduce(action, state),
|
||||
onboardingOtherCardsState = OnboardingOtherCardsReducer.reduce(action, state),
|
||||
twinCardsState = TwinCardsReducer.reduce(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
walletConnectState = WalletConnectReducer.reduce(action, state.walletConnectState),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
saveWalletState = SaveWalletReducer.reduce(action, state),
|
||||
daggerGraphState = DaggerGraphReducer.reduce(action, state),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,7 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectMiddlewa
|
|||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.features.home.redux.HomeMiddleware
|
||||
import com.tangem.tap.features.home.redux.HomeState
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletMiddleware
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
|
|
@ -31,14 +21,9 @@ import org.rekotlin.StateType
|
|||
data class AppState(
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val homeState: HomeState = HomeState(),
|
||||
val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(),
|
||||
val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(),
|
||||
val onboardingOtherCardsState: OnboardingOtherCardsState = OnboardingOtherCardsState(),
|
||||
val twinCardsState: TwinCardsState = TwinCardsState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val walletConnectState: WalletConnectState = WalletConnectState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
val saveWalletState: SaveWalletState = SaveWalletState(),
|
||||
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
|
||||
) : StateType {
|
||||
|
||||
|
|
@ -46,18 +31,12 @@ data class AppState(
|
|||
fun getMiddleware(): List<Middleware<AppState>> {
|
||||
return listOf(
|
||||
logMiddleware,
|
||||
notificationsMiddleware,
|
||||
GlobalMiddleware.handler,
|
||||
HomeMiddleware.handler,
|
||||
OnboardingNoteMiddleware.handler,
|
||||
OnboardingWalletMiddleware.handler,
|
||||
OnboardingOtherCardsMiddleware.handler,
|
||||
TwinCardsMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
WalletConnectMiddleware().walletConnectMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
WelcomeMiddleware().middleware,
|
||||
SaveWalletMiddleware().middleware,
|
||||
LockUserWalletsTimerMiddleware().middleware,
|
||||
AccessCodeRequestPolicyMiddleware().middleware,
|
||||
DaggerGraphMiddleware.daggerGraphMiddleware,
|
||||
|
|
|
|||
|
|
@ -1,141 +0,0 @@
|
|||
package com.tangem.tap.common.redux
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.tap.common.extensions.getColor
|
||||
import com.tangem.tap.domain.ArgError
|
||||
import com.tangem.tap.domain.MultiMessageError
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.assembleErrors
|
||||
import com.tangem.tap.notificationsHandler
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import com.tangem.wallet.R
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class NotificationsHandler(coordinatorLayout: CoordinatorLayout) {
|
||||
private val basicCoordinatorLayout = WeakReference(coordinatorLayout)
|
||||
private var baseLayout = basicCoordinatorLayout
|
||||
|
||||
private fun showNotification(message: String) {
|
||||
baseLayout.get()?.let { layout ->
|
||||
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
|
||||
.also { snackbar -> snackbar.show() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDebugNotification(message: String) {
|
||||
baseLayout.get()?.let { layout ->
|
||||
Snackbar.make(layout, message, Snackbar.LENGTH_LONG)
|
||||
.also { snackbar ->
|
||||
snackbar.setBackgroundTint(layout.getColor(R.color.warning_warning))
|
||||
snackbar.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showNotification(message: Int, args: List<Any>? = null) {
|
||||
baseLayout.get()?.let {
|
||||
showNotification(getMessageString(it.context, message, args))
|
||||
}
|
||||
}
|
||||
|
||||
fun showToastNotification(message: Int, args: List<Any>? = null) {
|
||||
baseLayout.get()?.let {
|
||||
Toast.makeText(it.context, getMessageString(it.context, message, args), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun showNotification(errorList: List<Pair<Int, List<Any>?>>, builder: (List<String>) -> String) {
|
||||
val context = baseLayout.get()?.context ?: return
|
||||
|
||||
val message = builder(errorList.map { getMessageString(context, it.first, it.second) })
|
||||
showNotification(message)
|
||||
}
|
||||
|
||||
fun showDebugErrorNotification(message: Int, args: List<Any>? = null) {
|
||||
baseLayout.get()?.let {
|
||||
showDebugNotification(getMessageString(it.context, message, args))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessageString(context: Context, message: Int, args: List<Any>?): String {
|
||||
return if (args.isNullOrEmpty()) {
|
||||
context.getString(message)
|
||||
} else {
|
||||
context.getString(message, *args.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
val notificationsMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleNotificationAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun handleNotificationAction(action: Action) {
|
||||
if (action is Debug && !BuildConfig.DEBUG) return
|
||||
|
||||
when (action) {
|
||||
is NotificationAction -> {
|
||||
notificationsHandler?.showNotification(action.messageResource)
|
||||
}
|
||||
is ToastNotificationAction -> notificationsHandler?.showToastNotification(action.messageResource)
|
||||
is ErrorAction -> {
|
||||
when (action) {
|
||||
is Debug -> {
|
||||
val args = (action.error as? ArgError)?.args ?: listOf()
|
||||
when (action) {
|
||||
is DebugNotification -> {
|
||||
notificationsHandler?.showNotification(action.error.messageResource, args)
|
||||
}
|
||||
is DebugToastNotification -> {
|
||||
notificationsHandler?.showToastNotification(action.error.messageResource, args)
|
||||
}
|
||||
is DebugErrorAction -> {
|
||||
notificationsHandler?.showDebugErrorNotification(action.error.messageResource, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when (action.error) {
|
||||
is MultiMessageError -> {
|
||||
val multiError = action.error as MultiMessageError
|
||||
notificationsHandler?.showNotification(multiError.assembleErrors(), multiError.builder)
|
||||
}
|
||||
else -> {
|
||||
val args = (action.error as? ArgError)?.args ?: listOf()
|
||||
notificationsHandler?.showNotification(action.error.messageResource, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ToastNotificationAction : Action {
|
||||
val messageResource: Int
|
||||
}
|
||||
|
||||
interface NotificationAction : Action {
|
||||
val messageResource: Int
|
||||
}
|
||||
|
||||
interface ErrorAction : Action {
|
||||
val error: TapError
|
||||
}
|
||||
|
||||
// Processed only in the debug builds
|
||||
interface Debug
|
||||
interface DebugNotification : Debug, NotificationAction
|
||||
interface DebugToastNotification : Debug, ToastNotificationAction
|
||||
interface DebugErrorAction : Debug, ErrorAction
|
||||
|
|
@ -5,45 +5,14 @@ import com.tangem.core.analytics.models.AnalyticsParam
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.common.redux.DebugErrorAction
|
||||
import com.tangem.tap.common.redux.ErrorAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class GlobalAction : Action {
|
||||
|
||||
// notifications
|
||||
data class ShowErrorNotification(override val error: TapError) : GlobalAction(), ErrorAction
|
||||
data class DebugShowErrorNotification(override val error: TapError) : GlobalAction(), DebugErrorAction
|
||||
|
||||
// dialogs
|
||||
data class ShowDialog(val stateDialog: StateDialog) : GlobalAction()
|
||||
object HideDialog : GlobalAction()
|
||||
|
||||
sealed class Onboarding : GlobalAction() {
|
||||
/**
|
||||
* Initiate an onboarding process.
|
||||
* For resuming unfinished backup of standard Wallet see
|
||||
* BackupAction.CheckForUnfinishedBackup, GlobalAction.Onboarding.StartForUnfinishedBackup
|
||||
*/
|
||||
data class Start(
|
||||
val scanResponse: ScanResponse,
|
||||
val source: BackupStartedSource,
|
||||
val canSkipBackup: Boolean = true,
|
||||
) : Onboarding()
|
||||
|
||||
/**
|
||||
* Initiate resuming of unfinished backup for standard Wallet.
|
||||
* See more BackupAction.CheckForUnfinishedBackup
|
||||
*/
|
||||
data class StartForUnfinishedBackup(val addedBackupCardsCount: Int) : Onboarding()
|
||||
|
||||
object Stop : Onboarding()
|
||||
|
||||
data class ShouldResetCardOnCreate(val shouldReset: Boolean) : Onboarding()
|
||||
}
|
||||
|
||||
object ScanFailsCounter {
|
||||
data class ChooseBehavior(
|
||||
val result: CompletionResult<ScanResponse>,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.common.redux.global
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.onboarding.OnboardingManager
|
||||
import com.tangem.utils.extensions.replaceBy
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -12,21 +11,6 @@ fun globalReducer(action: Action, state: AppState): GlobalState {
|
|||
val globalState = state.globalState
|
||||
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
val onboardingManager = OnboardingManager(action.scanResponse)
|
||||
globalState.copy(onboardingState = OnboardingState(true, onboardingManager))
|
||||
}
|
||||
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
|
||||
globalState.copy(onboardingState = OnboardingState(true, null))
|
||||
}
|
||||
is GlobalAction.Onboarding.Stop -> {
|
||||
globalState.copy(onboardingState = OnboardingState(false))
|
||||
}
|
||||
is GlobalAction.Onboarding.ShouldResetCardOnCreate -> {
|
||||
globalState.copy(
|
||||
onboardingState = globalState.onboardingState.copy(shouldResetOnCreate = action.shouldReset),
|
||||
)
|
||||
}
|
||||
is GlobalAction.ScanFailsCounter.Increment -> {
|
||||
globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ import com.tangem.domain.redux.LegacyAction
|
|||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.AppSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -26,15 +24,6 @@ internal object LegacyMiddleware {
|
|||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is LegacyAction.StartOnboardingProcess -> {
|
||||
store.dispatch(
|
||||
GlobalAction.Onboarding.Start(
|
||||
scanResponse = action.scanResponse,
|
||||
source = BackupStartedSource.CreateBackup,
|
||||
canSkipBackup = action.canSkipBackup,
|
||||
),
|
||||
)
|
||||
}
|
||||
is LegacyAction.PrepareDetailsScreen -> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
|
|
|||
|
|
@ -1,118 +0,0 @@
|
|||
package com.tangem.tap.common.toggleWidget
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.*
|
||||
import android.widget.ViewSwitcher
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
class RefreshBalanceWidget(root: ViewGroup) : ViewStateWidget {
|
||||
|
||||
var isShowing: Boolean? = null
|
||||
private set
|
||||
|
||||
override val mainView: View = root.findViewById(R.id.btn_refresh_balance)
|
||||
|
||||
private val viewSwitcher: ViewSwitcher by lazy { mainView.findViewById(R.id.switcher_refresh_arrow_to_progress) }
|
||||
|
||||
private val arrowView = mainView.findViewById<View>(R.id.imv_arrow_refresh)
|
||||
|
||||
private var progressViewAnimation: Animation? = null
|
||||
|
||||
init {
|
||||
viewSwitcher.inAnimation = AlphaAnimation(0f, 1f).apply {
|
||||
duration = 400
|
||||
interpolator = LinearInterpolator()
|
||||
}
|
||||
viewSwitcher.outAnimation = AlphaAnimation(1f, 0f).apply {
|
||||
duration = 400
|
||||
interpolator = LinearInterpolator()
|
||||
}
|
||||
}
|
||||
|
||||
override fun changeState(state: WidgetState) {
|
||||
val progressState = state as? ProgressState ?: return
|
||||
|
||||
val currentStateByView = getState()
|
||||
when {
|
||||
currentStateByView == progressState -> return
|
||||
currentStateByView == ProgressState.Done -> if (progressState == ProgressState.Error) return
|
||||
}
|
||||
|
||||
mainView.isClickable = currentStateByView == ProgressState.Done || currentStateByView == ProgressState.Error
|
||||
animateState(progressState)
|
||||
viewSwitcher.showNext()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun animateState(state: ProgressState) {
|
||||
when (state) {
|
||||
ProgressState.Done, ProgressState.Error -> {
|
||||
progressViewAnimation?.cancel()
|
||||
progressViewAnimation = null
|
||||
}
|
||||
ProgressState.Loading -> {
|
||||
progressViewAnimation = RotateAnimation(
|
||||
/* fromDegrees = */
|
||||
0f,
|
||||
/* toDegrees = */
|
||||
360f,
|
||||
/* pivotXType = */
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
/* pivotXValue = */
|
||||
0.5f,
|
||||
/* pivotYType = */
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
/* pivotYValue = */
|
||||
0.5f,
|
||||
)
|
||||
progressViewAnimation?.duration = 700
|
||||
progressViewAnimation?.interpolator = AccelerateInterpolator()
|
||||
progressViewAnimation?.repeatCount = -1
|
||||
progressViewAnimation?.let { arrowView.startAnimation(it) }
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
fun show(show: Boolean) {
|
||||
if (show == isShowing) return
|
||||
|
||||
isShowing = show
|
||||
if (show) {
|
||||
mainView.startAnimation(ShowAnimation())
|
||||
} else {
|
||||
mainView.startAnimation(AlphaAnimation(1f, 0f).apply { fillAfter = true })
|
||||
}
|
||||
}
|
||||
|
||||
private fun isArrowRefreshActive(): Boolean = viewSwitcher.currentView.id == R.id.fl_arrow_refresh_container
|
||||
|
||||
private fun getState(): ProgressState = if (isArrowRefreshActive()) ProgressState.Done else ProgressState.Loading
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
class ShowAnimation : AnimationSet(true) {
|
||||
init {
|
||||
addAnimation(
|
||||
ScaleAnimation(
|
||||
0f,
|
||||
1f,
|
||||
0f,
|
||||
1f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
Animation.RELATIVE_TO_SELF,
|
||||
0.5f,
|
||||
),
|
||||
)
|
||||
addAnimation(AlphaAnimation(0f, 1f))
|
||||
duration = 300
|
||||
interpolator = AnticipateOvershootInterpolator()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
package com.tangem.tap.common.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.domain.model.WalletAddressData
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.DialogOnboardingAddressInfoBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AddressInfoBottomSheetDialog(
|
||||
private val stateDialog: AppDialog.AddressInfoDialog,
|
||||
context: Context,
|
||||
) : BottomSheetDialog(context) {
|
||||
|
||||
var binding: DialogOnboardingAddressInfoBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = DialogOnboardingAddressInfoBinding
|
||||
.inflate(LayoutInflater.from(context))
|
||||
setContentView(binding!!.root)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
setOnCancelListener {
|
||||
store.dispatchDialogHide()
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun show() {
|
||||
super.show()
|
||||
Analytics.send(Token.Receive.ScreenOpened(stateDialog.currency.currencySymbol))
|
||||
showData(data = stateDialog.addressData)
|
||||
}
|
||||
|
||||
private fun showData(data: WalletAddressData) = with(binding!!) {
|
||||
pseudoToolbar.imvClose.setOnClickListener {
|
||||
dismissWithAnimation = true
|
||||
cancel()
|
||||
}
|
||||
imvQrCode.setImageBitmap(data.shareUrl.toQrCode())
|
||||
tvAddress.text = data.address
|
||||
btnFlCopyAddress.setOnClickListener {
|
||||
Analytics.send(Token.Receive.ButtonCopyAddress())
|
||||
|
||||
Toast
|
||||
.makeText(context, R.string.wallet_notification_address_copied, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
|
||||
store.inject(DaggerGraphState::clipboardManager).setText(text = data.address, isSensitive = true)
|
||||
}
|
||||
btnFlShare.setOnClickListener {
|
||||
Analytics.send(Token.Receive.ButtonShareAddress())
|
||||
store.dispatchShare(data.shareUrl)
|
||||
}
|
||||
val blockchain = stateDialog.currency.blockchain
|
||||
tvReceiveMessage.text = tvReceiveMessage.getString(
|
||||
id = R.string.address_qr_code_message_format,
|
||||
blockchain.getCoinName(),
|
||||
blockchain.currency,
|
||||
blockchain.fullName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,18 +20,22 @@ internal class DefaultVisaAuthTokenStorage @Inject constructor(
|
|||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : VisaAuthTokenStorage {
|
||||
|
||||
private val secureStorage = AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_auth_storage",
|
||||
),
|
||||
)
|
||||
private val secureStorage by lazy {
|
||||
AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_auth_storage",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
private val moshi by lazy {
|
||||
Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
|
||||
private val tokensAdapter = moshi.adapter(VisaAuthTokens::class.java)
|
||||
private val tokensAdapter by lazy { moshi.adapter(VisaAuthTokens::class.java) }
|
||||
|
||||
override suspend fun store(cardId: String, tokens: VisaAuthTokens) = withContext(dispatcherProvider.io) {
|
||||
val json = tokensAdapter.toJson(tokens)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,14 @@ class DefaultVisaOTPStorage @Inject constructor(
|
|||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : VisaOTPStorage {
|
||||
|
||||
private val secureStorage = AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_otp_storage",
|
||||
),
|
||||
)
|
||||
private val secureStorage by lazy {
|
||||
AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
storageName = "visa_otp_storage",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun saveOTP(cardId: String, data: VisaOtpData) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.store(data.rootOTP, VISA_ROOT_OTP_KEY_PREFIX + cardId)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.tap.data
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import kotlinx.coroutines.tasks.await
|
||||
|
||||
internal class FirebasePushNotificationsTokenProvider : PushNotificationsTokenProvider {
|
||||
override suspend fun getToken(): String {
|
||||
return FirebaseMessaging.getInstance().token.await()
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ internal class RuntimeUserWalletsStore(
|
|||
return userWalletsListManager.userWalletsSync.firstOrNull { it.walletId == key }
|
||||
}
|
||||
|
||||
override suspend fun getSyncStrict(key: UserWalletId): UserWallet {
|
||||
override fun getSyncStrict(key: UserWalletId): UserWallet {
|
||||
return requireNotNull(getSyncOrNull(key)) { "Unable to find user wallet with provided ID: $key" }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tap.di.data
|
||||
|
||||
import com.tangem.tap.data.FirebasePushNotificationsTokenProvider
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface PushNotificationsModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindPushNotificationsTokenProvider(impl: FirebasePushNotificationsTokenProvider): PushNotificationsTokenProvider
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import com.tangem.domain.managetokens.*
|
|||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -73,6 +75,8 @@ internal object ManageTokensDomainModule {
|
|||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): SaveManagedTokensUseCase {
|
||||
return SaveManagedTokensUseCase(
|
||||
|
|
@ -84,6 +88,8 @@ internal object ManageTokensDomainModule {
|
|||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import com.tangem.domain.card.repository.DerivationsRepository
|
|||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -49,8 +52,16 @@ object MarketsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTokenQuotesUseCase(quotesRepository: QuotesRepository): GetCurrencyQuotesUseCase {
|
||||
return GetCurrencyQuotesUseCase(quotesRepository = quotesRepository)
|
||||
fun provideGetTokenQuotesUseCase(
|
||||
quotesRepository: QuotesRepository,
|
||||
singleQuoteSupplier: SingleQuoteSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): GetCurrencyQuotesUseCase {
|
||||
return GetCurrencyQuotesUseCase(
|
||||
quotesRepository = quotesRepository,
|
||||
singleQuoteSupplier = singleQuoteSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -63,6 +74,8 @@ object MarketsDomainModule {
|
|||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): SaveMarketTokensUseCase {
|
||||
return SaveMarketTokensUseCase(
|
||||
|
|
@ -73,6 +86,8 @@ object MarketsDomainModule {
|
|||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.crypto.bip39.Wordlist
|
||||
import com.tangem.feature.onboarding.data.DefaultMnemonicRepository
|
||||
import com.tangem.feature.onboarding.data.MnemonicRepository
|
||||
import com.tangem.sdk.extensions.getWordlist
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class MnemonicModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMnemonicRepository(@ApplicationContext context: Context): MnemonicRepository {
|
||||
return DefaultMnemonicRepository(Wordlist.getWordlist(context))
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,16 @@ internal object NFTDomainModule {
|
|||
nftRepository = nftRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesRefreshAllNFTUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
): RefreshAllNFTUseCase = RefreshAllNFTUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesFetchNFTCollectionAssetsUseCase(nftRepository: NFTRepository): FetchNFTCollectionAssetsUseCase =
|
||||
|
|
@ -47,7 +57,7 @@ internal object NFTDomainModule {
|
|||
fun providesGetNFTAvailableNetworksUseCase(
|
||||
nftRepository: NFTRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): GetNFTAvailableNetworksUseCase = GetNFTAvailableNetworksUseCase(
|
||||
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
|
|
@ -66,4 +76,11 @@ internal object NFTDomainModule {
|
|||
GetNFTNetworkStatusUseCase(
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetNFTExploreUrlUseCase(nftRepository: NFTRepository): GetNFTExploreUrlUseCase =
|
||||
GetNFTExploreUrlUseCase(
|
||||
nftRepository = nftRepository,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
||||
import com.tangem.domain.notifications.SendPushTokenUseCase
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NotificationsDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetApplicationIdUseCase(notificationsRepository: NotificationsRepository): GetApplicationIdUseCase =
|
||||
GetApplicationIdUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSendPushTokenUseCase(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
getApplicationIdUseCase: GetApplicationIdUseCase,
|
||||
pushNotificationsTokenProvider: PushNotificationsTokenProvider,
|
||||
): SendPushTokenUseCase = SendPushTokenUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
getApplicationIdUseCase = getApplicationIdUseCase,
|
||||
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.repositories.*
|
||||
import com.tangem.domain.staking.repositories.StakingActionRepository
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -92,10 +96,12 @@ internal object StakingDomainModule {
|
|||
fun provideFetchStakingYieldBalanceUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
): FetchStakingYieldBalanceUseCase {
|
||||
return FetchStakingYieldBalanceUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,13 @@ import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
|||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.quotes.QuotesRepositoryV2
|
||||
import com.tangem.domain.quotes.multi.MultiQuoteFetcher
|
||||
import com.tangem.domain.quotes.single.SingleQuoteSupplier
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.operations.BaseCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
|
|
@ -35,6 +41,8 @@ internal object TokensDomainModule {
|
|||
stakingRepository: StakingRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): AddCryptoCurrenciesUseCase {
|
||||
return AddCryptoCurrenciesUseCase(
|
||||
|
|
@ -43,6 +51,8 @@ internal object TokensDomainModule {
|
|||
stakingRepository = stakingRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -55,6 +65,8 @@ internal object TokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchTokenListUseCase {
|
||||
return FetchTokenListUseCase(
|
||||
|
|
@ -63,6 +75,8 @@ internal object TokensDomainModule {
|
|||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -168,6 +182,8 @@ internal object TokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
singleYieldBalanceFetcher: SingleYieldBalanceFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchCurrencyStatusUseCase {
|
||||
return FetchCurrencyStatusUseCase(
|
||||
|
|
@ -176,6 +192,8 @@ internal object TokensDomainModule {
|
|||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
singleYieldBalanceFetcher = singleYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -188,6 +206,8 @@ internal object TokensDomainModule {
|
|||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): FetchCardTokenListUseCase {
|
||||
return FetchCardTokenListUseCase(
|
||||
|
|
@ -196,6 +216,8 @@ internal object TokensDomainModule {
|
|||
quotesRepository = quotesRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -364,10 +386,14 @@ internal object TokensDomainModule {
|
|||
fun provideRefreshMultiCurrencyWalletQuotesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): RefreshMultiCurrencyWalletQuotesUseCase {
|
||||
return RefreshMultiCurrencyWalletQuotesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -386,20 +412,30 @@ internal object TokensDomainModule {
|
|||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
quotesRepositoryV2: QuotesRepositoryV2,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
singleQuoteSupplier: SingleQuoteSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
): BaseCurrenciesStatusesOperations {
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
quotesRepositoryV2 = quotesRepositoryV2,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
singleQuoteSupplier = singleQuoteSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
@ -410,21 +446,37 @@ internal object TokensDomainModule {
|
|||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
quotesRepositoryV2: QuotesRepositoryV2,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
multiQuoteFetcher: MultiQuoteFetcher,
|
||||
singleQuoteSupplier: SingleQuoteSupplier,
|
||||
singleYieldBalanceSupplier: SingleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
||||
): BaseCurrencyStatusOperations {
|
||||
return CachedCurrenciesStatusesOperations(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
quotesRepositoryV2 = quotesRepositoryV2,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
||||
multiQuoteFetcher = multiQuoteFetcher,
|
||||
singleQuoteSupplier = singleQuoteSupplier,
|
||||
singleYieldBalanceSupplier = singleYieldBalanceSupplier,
|
||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
|
||||
return GetCryptoCurrenciesUseCase(currenciesRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,21 @@ internal object TransactionDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetEthSpecificFeeUseCase(walletManagersFacade: WalletManagersFacade): GetEthSpecificFeeUseCase {
|
||||
return GetEthSpecificFeeUseCase(walletManagersFacade = walletManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTransferGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetTransferFeeUseCase {
|
||||
return GetTransferFeeUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
demoConfig = DemoConfig(),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendTransactionUseCase(
|
||||
|
|
@ -147,4 +162,38 @@ internal object TransactionDomainModule {
|
|||
fun provideGetAllowanceUseCase(transactionRepository: TransactionRepository): GetAllowanceUseCase {
|
||||
return GetAllowanceUseCase(transactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCreateTransferTransactionUseCase(
|
||||
transactionRepository: TransactionRepository,
|
||||
): CreateTransferTransactionUseCase {
|
||||
return CreateTransferTransactionUseCase(transactionRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePrepareForSendUseCase(
|
||||
transactionRepository: TransactionRepository,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
): PrepareForSendUseCase {
|
||||
return PrepareForSendUseCase(transactionRepository, cardSdkConfigRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSignUseCase(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
): SignUseCase {
|
||||
return SignUseCase(cardSdkConfigRepository, walletManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCreateNFTTransferTransactionUseCase(
|
||||
transactionRepository: TransactionRepository,
|
||||
): CreateNFTTransferTransactionUseCase {
|
||||
return CreateNFTTransferTransactionUseCase(transactionRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.tap.di.domain
|
|||
|
||||
import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase
|
||||
import com.tangem.domain.walletconnect.repository.WalletConnectRepository
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import com.tangem.domain.walletconnect.usecase.WcSessionsUseCase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -19,4 +21,10 @@ internal object WalletConnectDomainModule {
|
|||
): CheckIsWalletConnectAvailableUseCase {
|
||||
return CheckIsWalletConnectAvailableUseCase(walletConnectRepository = walletConnectRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWcSessionsUseCase(sessionsManager: WcSessionsManager): WcSessionsUseCase {
|
||||
return WcSessionsUseCase(sessionsManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSy
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -172,8 +174,16 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCardImageUseCase(onlineCardVerifier: OnlineCardVerifier): GetCardImageUseCase {
|
||||
return GetCardImageUseCase(verifier = onlineCardVerifier)
|
||||
fun provideGetCardImageUseCase(
|
||||
onlineCardVerifier: OnlineCardVerifier,
|
||||
cardArtworksProvider: CardArtworksProvider,
|
||||
cardSdkFeatureToggles: CardSdkFeatureToggles,
|
||||
): GetCardImageUseCase {
|
||||
return GetCardImageUseCase(
|
||||
verifier = onlineCardVerifier,
|
||||
cardArtworksProvider = cardArtworksProvider,
|
||||
cardSdkFeatureToggles = cardSdkFeatureToggles,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.domain.common.extensions.withMainContext
|
|||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
|
@ -42,7 +41,6 @@ class TapWalletManager(
|
|||
|
||||
withMainContext {
|
||||
// Order is important
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.common.TwinCardNumber
|
|||
import com.tangem.domain.common.getTwinCardNumber
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
|
||||
|
||||
|
|
@ -37,7 +38,7 @@ suspend fun CardDTO.getOrLoadCardArtworkUrl(
|
|||
if (artworkId.isNullOrEmpty()) {
|
||||
ifAnyError()
|
||||
} else {
|
||||
OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
|
||||
CardArtworksProvider.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.core.ui.message.dialog.Dialogs
|
|||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
|
|
@ -34,9 +33,6 @@ import com.tangem.tap.common.redux.AppDialog
|
|||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.disclaimer.createDisclaimer
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -208,8 +204,6 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
crossinline onCancel: suspend () -> Unit,
|
||||
crossinline onSuccess: suspend (ScanResponse) -> Unit,
|
||||
) {
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
|
||||
checkCardWasUsedInApp(
|
||||
scanResponse = scanResponse,
|
||||
onCancel = {
|
||||
|
|
@ -222,38 +216,26 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
Analytics.addContext(scanResponse)
|
||||
onWalletNotCreated()
|
||||
// must check skip backup using card canSkipBackup
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.Onboarding.Start(
|
||||
navigateTo(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
source = BackupStartedSource.Onboarding,
|
||||
canSkipBackup = scanResponse.card.canSkipBackup,
|
||||
mode = AppRoute.Onboarding.Mode.Onboarding,
|
||||
),
|
||||
)
|
||||
val appScreen = OnboardingHelper.whereToNavigate(scanResponse)
|
||||
navigateTo(appScreen) { onProgressStateChange(it) }
|
||||
) { onProgressStateChange(it) }
|
||||
} else {
|
||||
Analytics.setContext(scanResponse)
|
||||
|
||||
val isTwinRefactoringEnabled =
|
||||
store.inject(DaggerGraphState::onboardingV2FeatureToggles).isTwinRefactoringEnabled
|
||||
|
||||
val wasTwinsOnboardingShown =
|
||||
store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase).invokeSync()
|
||||
|
||||
if (scanResponse.twinsIsTwinned() && !wasTwinsOnboardingShown) {
|
||||
onWalletNotCreated()
|
||||
if (isTwinRefactoringEnabled) {
|
||||
navigateTo(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.WelcomeOnlyTwin,
|
||||
),
|
||||
) { onProgressStateChange(it) }
|
||||
} else {
|
||||
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse)))
|
||||
navigateTo(AppRoute.OnboardingTwins) { onProgressStateChange(it) }
|
||||
}
|
||||
navigateTo(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.WelcomeOnlyTwin,
|
||||
),
|
||||
) { onProgressStateChange(it) }
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
onSuccess(scanResponse)
|
||||
|
|
|
|||
|
|
@ -5,21 +5,15 @@ import arrow.core.right
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.common.TapWorkarounds.canSkipBackup
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.core.chain.ResultChain
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -40,46 +34,30 @@ class CheckForOnboardingChain(
|
|||
) : ResultChain<ScanCardException, ScanResponse>() {
|
||||
|
||||
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
|
||||
store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(previousChainResult))
|
||||
|
||||
return when {
|
||||
OnboardingHelper.isOnboardingCase(previousChainResult) -> {
|
||||
Analytics.addContext(previousChainResult)
|
||||
// must check skip backup using card canSkipBackup
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.Onboarding.Start(
|
||||
ScanChainException.OnboardingNeeded(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = previousChainResult,
|
||||
source = BackupStartedSource.Onboarding,
|
||||
canSkipBackup = previousChainResult.card.canSkipBackup,
|
||||
mode = AppRoute.Onboarding.Mode.Onboarding,
|
||||
),
|
||||
)
|
||||
val route = OnboardingHelper.whereToNavigate(previousChainResult)
|
||||
ScanChainException.OnboardingNeeded(route).left()
|
||||
).left()
|
||||
}
|
||||
else -> {
|
||||
Analytics.setContext(previousChainResult)
|
||||
|
||||
val isTwinRefactoringEnabled =
|
||||
store.inject(DaggerGraphState::onboardingV2FeatureToggles).isTwinRefactoringEnabled
|
||||
|
||||
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
|
||||
.invokeSync()
|
||||
|
||||
// If twins was twinned previously but twins welcome not shown
|
||||
if (previousChainResult.twinsIsTwinned() && !wasTwinsOnboardingShown) {
|
||||
if (isTwinRefactoringEnabled) {
|
||||
ScanChainException.OnboardingNeeded(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = previousChainResult,
|
||||
mode = AppRoute.Onboarding.Mode.WelcomeOnlyTwin,
|
||||
),
|
||||
).left()
|
||||
} else {
|
||||
store.dispatchOnMain(
|
||||
TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(previousChainResult)),
|
||||
)
|
||||
ScanChainException.OnboardingNeeded(AppRoute.OnboardingTwins).left()
|
||||
}
|
||||
ScanChainException.OnboardingNeeded(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = previousChainResult,
|
||||
mode = AppRoute.Onboarding.Mode.WelcomeOnlyTwin,
|
||||
),
|
||||
).left()
|
||||
} else {
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
previousChainResult.right()
|
||||
|
|
|
|||
|
|
@ -4,14 +4,12 @@ import arrow.core.Either
|
|||
import arrow.core.getOrElse
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.blockchain.common.UnmarshalHelper
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.*
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.map
|
||||
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
|
||||
|
|
@ -46,7 +44,7 @@ import timber.log.Timber
|
|||
import kotlin.coroutines.resume
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
class VisaCardActivationTask @AssistedInject constructor(
|
||||
@Assisted private val mode: VisaCardActivationTaskMode,
|
||||
@Assisted private val activationInput: VisaActivationInput,
|
||||
|
|
@ -97,7 +95,23 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
context.signAuthorizationChallenge(mode.authorizationChallenge)
|
||||
}
|
||||
is VisaCardActivationTaskMode.SignOnly -> {
|
||||
context.signData(mode.dataToSignByCardWallet)
|
||||
val wallet =
|
||||
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val derivedPublicKey = when (val deriveKeyResult = context.deriveKey(wallet.publicKey)) {
|
||||
is CompletionResult.Failure -> {
|
||||
return CompletionResult.Failure(deriveKeyResult.error)
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
deriveKeyResult.data
|
||||
}
|
||||
}
|
||||
|
||||
context.signData(
|
||||
mode.dataToSignByCardWallet,
|
||||
derivedPublicKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -138,9 +152,35 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
private suspend fun SessionContext.processSignedAuthorizationChallenge(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
when (val createWalletResult = createWallet()) {
|
||||
is CompletionResult.Failure -> return CompletionResult.Failure(createWalletResult.error)
|
||||
is CompletionResult.Success -> {}
|
||||
}
|
||||
|
||||
val card =
|
||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val wallet =
|
||||
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) {
|
||||
is CompletionResult.Failure -> {
|
||||
return CompletionResult.Failure(deriveKeyResult.error)
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
deriveKeyResult.data
|
||||
}
|
||||
}
|
||||
|
||||
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
|
||||
.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||
.value
|
||||
|
||||
return coroutineScope {
|
||||
val dataToSignDeferred = async { getDataToSign(signedChallenge) }
|
||||
val otpTaskDeferred = async { createWallet() }
|
||||
val dataToSignDeferred =
|
||||
async { getDataToSign(signedChallenge = signedChallenge, cardWalletAddress = walletAddress) }
|
||||
val otpTaskDeferred = async { createOTP() }
|
||||
|
||||
val dataToSign = dataToSignDeferred.await()
|
||||
.getOrElse {
|
||||
|
|
@ -150,12 +190,16 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
otpTaskDeferred.await()
|
||||
|
||||
signData(dataToSign)
|
||||
signData(
|
||||
dataToSign = dataToSign,
|
||||
derivedPublicKey = derivedPublicKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun SessionContext.getDataToSign(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
cardWalletAddress: String,
|
||||
): Either<TangemError, VisaDataToSignByCardWallet> = either {
|
||||
catch(
|
||||
block = {
|
||||
|
|
@ -168,7 +212,12 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
raise(VisaActivationError.WrongRemoteState.tangemError)
|
||||
}
|
||||
|
||||
visaActivationRepository.getCardWalletAcceptanceData(remoteState.request)
|
||||
visaActivationRepository.getCardWalletAcceptanceData(
|
||||
VisaCardWalletDataToSignRequest(
|
||||
activationOrderInfo = remoteState.activationOrderInfo,
|
||||
cardWalletAddress = cardWalletAddress,
|
||||
),
|
||||
)
|
||||
},
|
||||
catch = {
|
||||
raise(VisaAuthorizationAPIError.tangemError)
|
||||
|
|
@ -182,7 +231,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
return if (card.wallets.any { it.curve == EllipticCurve.Secp256k1 }) {
|
||||
createOTP()
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
val createWalletTask = CreateWalletTask(EllipticCurve.Secp256k1)
|
||||
|
||||
|
|
@ -199,7 +248,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("CreateWalletTask success")
|
||||
createOTP()
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("CreateWalletTask failure ${result.error}")
|
||||
|
|
@ -245,26 +294,15 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
private suspend fun SessionContext.signData(
|
||||
dataToSign: VisaDataToSignByCardWallet,
|
||||
derivedPublicKey: ExtendedPublicKey,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val card =
|
||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val wallet =
|
||||
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) {
|
||||
is CompletionResult.Failure -> {
|
||||
return CompletionResult.Failure(deriveKeyResult.error)
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
deriveKeyResult.data
|
||||
}
|
||||
}
|
||||
|
||||
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
|
||||
.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||
.value
|
||||
|
||||
val task = SignHashCommand(
|
||||
hash = dataToSign.hashToSign.hexToBytes(),
|
||||
walletPublicKey = wallet.publicKey,
|
||||
|
|
@ -286,8 +324,8 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
Timber.i("SignHashCommand success")
|
||||
handleSignedData(
|
||||
dataToSign = dataToSign,
|
||||
walletAddress = walletAddress,
|
||||
response = result.data,
|
||||
derivedPublicKey = derivedPublicKey,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -313,7 +351,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
private suspend fun SessionContext.handleSignedData(
|
||||
dataToSign: VisaDataToSignByCardWallet,
|
||||
walletAddress: String,
|
||||
derivedPublicKey: ExtendedPublicKey,
|
||||
response: SignHashResponse,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val otp = otpStorage.getOTP(cardId) ?: run {
|
||||
|
|
@ -321,11 +359,16 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(VisaActivationError.MissingRootOTP.tangemError)
|
||||
}
|
||||
|
||||
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
|
||||
signature = response.signature,
|
||||
hash = dataToSign.hashToSign.hexToBytes(),
|
||||
publicKey = derivedPublicKey.publicKey.toDecompressedPublicKey(),
|
||||
).asRSVLegacyEVM().toHexString()
|
||||
|
||||
val signedActivationData = dataToSign.sign(
|
||||
cardWalletAddress = walletAddress,
|
||||
rootOTP = otp.rootOTP.toHexString(),
|
||||
otpCounter = otp.counter,
|
||||
signature = response.signature.toHexString(),
|
||||
signature = rsvSignature,
|
||||
)
|
||||
|
||||
return setupAccessCode().map {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.UnmarshalHelper
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
|
|
@ -10,6 +11,7 @@ import com.tangem.common.core.CardSessionRunnable
|
|||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
|
|
@ -129,6 +131,7 @@ class VisaCustomerWalletApproveTask(
|
|||
targetWalletPublicKey = wallet.publicKey,
|
||||
derivationPath = derivationPath,
|
||||
session = session,
|
||||
extendedPublicKey = extendedPublicKey,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
|
|
@ -149,6 +152,7 @@ class VisaCustomerWalletApproveTask(
|
|||
signApproveData(
|
||||
targetWalletPublicKey = publicKey,
|
||||
derivationPath = null,
|
||||
extendedPublicKey = null,
|
||||
session = session,
|
||||
callback = callback,
|
||||
)
|
||||
|
|
@ -157,11 +161,14 @@ class VisaCustomerWalletApproveTask(
|
|||
private fun signApproveData(
|
||||
targetWalletPublicKey: ByteArray,
|
||||
derivationPath: DerivationPath?,
|
||||
extendedPublicKey: ExtendedPublicKey?,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val hashToSign = visaDataForApprove.dataToSign.hashToSign.hexToBytes()
|
||||
|
||||
val signTask = SignHashCommand(
|
||||
hash = visaDataForApprove.dataToSign.hashToSign.hexToBytes(),
|
||||
hash = hashToSign,
|
||||
walletPublicKey = targetWalletPublicKey,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
|
@ -169,11 +176,18 @@ class VisaCustomerWalletApproveTask(
|
|||
signTask.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
|
||||
signature = result.data.signature,
|
||||
hash = hashToSign,
|
||||
publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey()
|
||||
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
||||
).asRSVLegacyEVM().toHexString()
|
||||
|
||||
scanCard(
|
||||
session = session,
|
||||
callback = callback,
|
||||
signedData = visaDataForApprove.dataToSign.sign(
|
||||
signature = result.data.signature.toHexString(),
|
||||
signature = rsvSignature,
|
||||
customerWalletAddress = visaDataForApprove.targetAddress,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,4 +9,10 @@ internal class DefaultTokensFeatureToggles(
|
|||
|
||||
override val isNetworksLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "NETWORKS_LOADING_REFACTORING_ENABLED")
|
||||
|
||||
override val isQuotesLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "QUOTES_LOADING_REFACTORING_ENABLED")
|
||||
|
||||
override val isStakingLoadingRefactoringEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "STAKING_LOADING_REFACTORING_ENABLED")
|
||||
}
|
||||
|
|
@ -21,8 +21,6 @@ internal data class UserWalletPublicInformation(
|
|||
val name: String,
|
||||
@Json(name = "walletId")
|
||||
val walletId: UserWalletId,
|
||||
@Json(name = "artworkUrl")
|
||||
val artworkUrl: String,
|
||||
@Json(name = "cardsInWallet")
|
||||
val cardsInWallet: Set<String>,
|
||||
@Json(name = "scanResponse")
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ internal class DefaultUserWalletsPublicInformationRepository(
|
|||
moshi: Moshi,
|
||||
private val secureStorage: SecureStorage,
|
||||
) : UserWalletsPublicInformationRepository {
|
||||
private val publicInformationAdapter: JsonAdapter<List<UserWalletPublicInformation>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, UserWalletPublicInformation::class.java),
|
||||
)
|
||||
|
||||
private val publicInformationAdapter: JsonAdapter<List<UserWalletPublicInformation>> by lazy {
|
||||
moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, UserWalletPublicInformation::class.java),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,15 @@ internal class DefaultUserWalletsSensitiveInformationRepository(
|
|||
private val secureStorage: SecureStorage,
|
||||
) : UserWalletsSensitiveInformationRepository {
|
||||
|
||||
private val sensitiveInformationAdapter: JsonAdapter<UserWalletSensitiveInformation> = moshi.adapter(
|
||||
UserWalletSensitiveInformation::class.java,
|
||||
)
|
||||
private val encryptedSensitiveInformationMapAdapter: JsonAdapter<Map<String, ByteArray>> = moshi.adapter(
|
||||
Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java),
|
||||
)
|
||||
private val sensitiveInformationAdapter: JsonAdapter<UserWalletSensitiveInformation> by lazy {
|
||||
moshi.adapter(UserWalletSensitiveInformation::class.java)
|
||||
}
|
||||
|
||||
private val encryptedSensitiveInformationMapAdapter: JsonAdapter<Map<String, ByteArray>> by lazy {
|
||||
moshi.adapter(
|
||||
Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult<Unit> {
|
||||
if (encryptionKey == null) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ internal val UserWallet.publicInformation: UserWalletPublicInformation
|
|||
get() = UserWalletPublicInformation(
|
||||
name = name,
|
||||
walletId = walletId,
|
||||
artworkUrl = artworkUrl,
|
||||
cardsInWallet = cardsInWallet,
|
||||
isMultiCurrency = isMultiCurrency,
|
||||
scanResponse = scanResponse.copy(
|
||||
|
|
@ -31,7 +30,6 @@ internal fun UserWalletPublicInformation.toUserWallet(): UserWallet {
|
|||
return UserWallet(
|
||||
name = name,
|
||||
walletId = walletId,
|
||||
artworkUrl = artworkUrl,
|
||||
cardsInWallet = cardsInWallet,
|
||||
scanResponse = scanResponse,
|
||||
isMultiCurrency = isMultiCurrency,
|
||||
|
|
|
|||
|
|
@ -128,6 +128,8 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
|
||||
Timber.i("Requesting challenge for wallet authorization")
|
||||
val challengeResponse = runCatching {
|
||||
// TODO [REDACTED_TASK_KEY]
|
||||
error("sign and get specific error to switch to card_id flow")
|
||||
visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
|
||||
}.getOrElse {
|
||||
Timber.i(
|
||||
|
|
@ -180,6 +182,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
return CompletionResult.Success(VisaCardActivationStatus.Activated(authorizationTokensResponse))
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private suspend fun SessionContext.handleCardAuthorization(
|
||||
cardWalletAddress: String,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
|
|
@ -232,11 +235,17 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
tokens = authorizationTokensResponse,
|
||||
)
|
||||
|
||||
val activationRemoteState = visaActivationRepository.getActivationRemoteState()
|
||||
val activationRemoteState = runCatching {
|
||||
visaActivationRepository.getActivationRemoteState()
|
||||
}.getOrElse {
|
||||
Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}")
|
||||
return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError)
|
||||
}
|
||||
|
||||
val error = when (activationRemoteState) {
|
||||
VisaActivationRemoteState.BlockedForActivation -> VisaActivationError.BlockedForActivation
|
||||
VisaActivationRemoteState.Activated -> VisaActivationError.InvalidActivationState
|
||||
VisaActivationRemoteState.Failed -> VisaActivationError.FailedRemoteState
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import com.tangem.Message
|
|||
import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.smartcontract.CompiledSmartContractCallData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.*
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
|
|
@ -16,6 +16,7 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
|
||||
import com.tangem.data.walletconnect.network.ethereum.LegacySdkHelper
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
|
|
@ -101,7 +102,7 @@ class WalletConnectSdkHelper {
|
|||
sourceAddress = transaction.from,
|
||||
destinationAddress = destinationAddress,
|
||||
extras = EthereumTransactionExtras(
|
||||
data = transaction.data.removePrefix(HEX_PREFIX).hexToBytes(),
|
||||
callData = CompiledSmartContractCallData(transaction.data.removePrefix(HEX_PREFIX).hexToBytes()),
|
||||
gasLimit = gasLimit.toBigInteger(),
|
||||
nonce = transaction.nonce?.hexToBigDecimal()?.toBigInteger(),
|
||||
),
|
||||
|
|
@ -191,7 +192,7 @@ class WalletConnectSdkHelper {
|
|||
val gasLimitResult = (walletManager as? EthereumGasLoader)?.getGasLimit(
|
||||
amount = Amount(value, walletManager.wallet.blockchain),
|
||||
destination = transaction.to ?: "",
|
||||
data = transaction.data,
|
||||
callData = CompiledSmartContractCallData(transaction.data.hexToBytes()),
|
||||
)
|
||||
return when (gasLimitResult) {
|
||||
is Result.Success -> gasLimitResult.data.toBigDecimal().multiply(BigDecimal("1.2"))
|
||||
|
|
@ -357,36 +358,9 @@ class WalletConnectSdkHelper {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createMessageData(message: WcSignMessage): ByteArray {
|
||||
val messageData = try {
|
||||
message.data.removePrefix(HEX_PREFIX).hexToBytes()
|
||||
} catch (exception: Exception) {
|
||||
message.data.asciiToHex()?.hexToBytes() ?: byteArrayOf()
|
||||
}
|
||||
private fun createMessageData(message: WcSignMessage): ByteArray = LegacySdkHelper.createMessageData(message.data)
|
||||
|
||||
val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray()
|
||||
return (prefixData + messageData).toKeccak()
|
||||
}
|
||||
|
||||
private fun String.hexToAscii(): String? {
|
||||
return try {
|
||||
removePrefix(HEX_PREFIX).hexToBytes()
|
||||
.map {
|
||||
val char = it.toInt().toChar()
|
||||
if (char.isAscii()) char else return null
|
||||
}
|
||||
.joinToString("")
|
||||
} catch (exception: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.asciiToHex(): String? {
|
||||
return map {
|
||||
if (!it.isAscii()) return null
|
||||
Integer.toHexString(it.code)
|
||||
}.joinToString("")
|
||||
}
|
||||
private fun String.hexToAscii(): String? = LegacySdkHelper.hexToAscii(hex = this)
|
||||
|
||||
suspend fun signPersonalMessage(
|
||||
hashToSign: ByteArray,
|
||||
|
|
@ -415,11 +389,11 @@ class WalletConnectSdkHelper {
|
|||
|
||||
return when (type) {
|
||||
WcSignMessage.WCSignType.SOLANA_MESSAGE -> getSolanaResultString(signedHash)
|
||||
else -> EthereumUtils.prepareSignedMessageData(
|
||||
signedHash = signedHash,
|
||||
hashToSign = hashToSign,
|
||||
else -> UnmarshalHelper.unmarshalSignatureExtended(
|
||||
signature = signedHash,
|
||||
hash = hashToSign,
|
||||
publicKey = wallet.publicKey.blockchainKey.toDecompressedPublicKey(),
|
||||
)
|
||||
).asRSVLegacyEVM().toHexString()
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
|
|
@ -535,7 +509,6 @@ class WalletConnectSdkHelper {
|
|||
"{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}"
|
||||
|
||||
private companion object {
|
||||
const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n"
|
||||
const val HEX_PREFIX = "0x"
|
||||
const val DEFAULT_MAX_GASLIMIT = 350000
|
||||
// TODO remove after [REDACTED_JIRA]
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import com.reown.walletkit.client.WalletKit
|
|||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.pair.unsupportedDApps
|
||||
import com.tangem.domain.walletconnect.model.legacy.Account
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
|
|
@ -21,8 +23,99 @@ import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction.O
|
|||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DefaultLegacyWalletConnectRepositoryFacade constructor(
|
||||
private val stub: LegacyWalletConnectRepositoryStub,
|
||||
private val legacy: DefaultLegacyWalletConnectRepository,
|
||||
private val walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
private val wcInitializeUseCase: WcInitializeUseCase,
|
||||
) : LegacyWalletConnectRepository {
|
||||
private val isNewWc by lazy {
|
||||
walletConnectFeatureToggles.isRedesignedWalletConnectEnabled
|
||||
}
|
||||
override val events: Flow<WalletConnectEvents> by lazy {
|
||||
if (isNewWc) stub.events else legacy.events
|
||||
}
|
||||
override val activeSessions: Flow<List<WalletConnectSession>> by lazy {
|
||||
if (isNewWc) stub.activeSessions else legacy.activeSessions
|
||||
}
|
||||
override val currentSessions: List<WalletConnectSession> by lazy {
|
||||
if (isNewWc) stub.currentSessions else legacy.currentSessions
|
||||
}
|
||||
|
||||
override fun init(projectId: String) {
|
||||
if (isNewWc) {
|
||||
wcInitializeUseCase.init(projectId)
|
||||
} else {
|
||||
legacy.init(projectId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>) {
|
||||
if (isNewWc) stub.setUserNamespaces(userNamespaces) else legacy.setUserNamespaces(userNamespaces)
|
||||
}
|
||||
|
||||
override fun updateSessions() {
|
||||
if (isNewWc) stub.updateSessions() else legacy.updateSessions()
|
||||
}
|
||||
|
||||
override fun pair(uri: String, source: SourceType) {
|
||||
if (isNewWc) stub.pair(uri, source) else legacy.pair(uri, source)
|
||||
}
|
||||
|
||||
override fun disconnect(topic: String) {
|
||||
if (isNewWc) stub.disconnect(topic) else legacy.disconnect(topic)
|
||||
}
|
||||
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>, blockchainNames: List<String>) {
|
||||
if (isNewWc) stub.approve(userNamespaces, blockchainNames) else legacy.approve(userNamespaces, blockchainNames)
|
||||
}
|
||||
|
||||
override fun reject() {
|
||||
if (isNewWc) stub.reject() else legacy.reject()
|
||||
}
|
||||
|
||||
override fun sendRequest(requestData: RequestData, result: String) {
|
||||
if (isNewWc) stub.sendRequest(requestData, result) else legacy.sendRequest(requestData, result)
|
||||
}
|
||||
|
||||
override fun rejectRequest(requestData: RequestData, error: WalletConnectError) {
|
||||
if (isNewWc) stub.rejectRequest(requestData, error) else legacy.rejectRequest(requestData, error)
|
||||
}
|
||||
|
||||
override fun cancelRequest(topic: String, id: Long, message: String) {
|
||||
if (isNewWc) stub.cancelRequest(topic, id, message) else legacy.cancelRequest(topic, id, message)
|
||||
}
|
||||
}
|
||||
|
||||
internal class LegacyWalletConnectRepositoryStub : LegacyWalletConnectRepository {
|
||||
override val events: Flow<WalletConnectEvents> = emptyFlow()
|
||||
override val activeSessions: Flow<List<WalletConnectSession>> = emptyFlow()
|
||||
override val currentSessions: List<WalletConnectSession> = listOf()
|
||||
|
||||
override fun init(projectId: String) = Unit
|
||||
|
||||
override fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>) = Unit
|
||||
|
||||
override fun updateSessions() = Unit
|
||||
|
||||
override fun pair(uri: String, source: SourceType) = Unit
|
||||
|
||||
override fun disconnect(topic: String) = Unit
|
||||
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>, blockchainNames: List<String>) = Unit
|
||||
|
||||
override fun reject() = Unit
|
||||
|
||||
override fun sendRequest(requestData: RequestData, result: String) = Unit
|
||||
|
||||
override fun rejectRequest(requestData: RequestData, error: WalletConnectError) = Unit
|
||||
|
||||
override fun cancelRequest(topic: String, id: Long, message: String) = Unit
|
||||
}
|
||||
|
||||
@Suppress("LargeClass")
|
||||
internal class DefaultLegacyWalletConnectRepository(
|
||||
private val application: Application,
|
||||
|
|
|
|||
|
|
@ -8,16 +8,21 @@ import com.tangem.datasource.di.SdkMoshi
|
|||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
||||
import com.tangem.tap.domain.walletconnect2.app.TangemWcBlockchainHelper
|
||||
import com.tangem.tap.domain.walletconnect2.app.WalletConnectEventsHandlerImpl
|
||||
import com.tangem.tap.domain.walletconnect2.data.DefaultLegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.data.DefaultLegacyWalletConnectRepositoryFacade
|
||||
import com.tangem.tap.domain.walletconnect2.data.DefaultWalletConnectSessionsRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.*
|
||||
import com.tangem.tap.domain.walletconnect2.data.LegacyWalletConnectRepositoryStub
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcJrpcRequestsDeserializer
|
||||
import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles
|
||||
import com.tangem.tap.domain.walletconnect2.toggles.DefaultWalletConnectFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -60,7 +65,7 @@ internal object WalletConnectModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletConnectFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletConnectFeatureToggles {
|
||||
return WalletConnectFeatureToggles(featureTogglesManager)
|
||||
return DefaultWalletConnectFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -69,12 +74,21 @@ internal object WalletConnectModule {
|
|||
application: Application,
|
||||
wcRequestDeserializer: WcJrpcRequestsDeserializer,
|
||||
analyticsHandler: AnalyticsEventHandler,
|
||||
walletConnectFeatureToggles: WalletConnectFeatureToggles,
|
||||
wcInitializeUseCase: WcInitializeUseCase,
|
||||
): LegacyWalletConnectRepository {
|
||||
return DefaultLegacyWalletConnectRepository(
|
||||
val legacy = DefaultLegacyWalletConnectRepository(
|
||||
application = application,
|
||||
wcRequestDeserializer = wcRequestDeserializer,
|
||||
analyticsHandler = analyticsHandler,
|
||||
)
|
||||
val stub = LegacyWalletConnectRepositoryStub()
|
||||
return DefaultLegacyWalletConnectRepositoryFacade(
|
||||
stub,
|
||||
legacy,
|
||||
walletConnectFeatureToggles,
|
||||
wcInitializeUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.tap.domain.walletconnect2.toggles
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles
|
||||
|
||||
internal class WalletConnectFeatureToggles(
|
||||
internal class DefaultWalletConnectFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) {
|
||||
) : WalletConnectFeatureToggles {
|
||||
|
||||
val isRedesignedWalletConnectEnabled: Boolean
|
||||
override val isRedesignedWalletConnectEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("WALLET_CONNECT_REDESIGN_ENABLED")
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
package com.tangem.tap.features
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.addCallback
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.MenuHost
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.transition.TransitionInflater
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackPressedHandler {
|
||||
|
||||
private lateinit var mainView: View
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
configureTransitions()
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
mainView = super.onCreateView(inflater, container, savedInstanceState)!!
|
||||
return mainView
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
loadToolbarMenu()?.let {
|
||||
val menuHost = requireActivity() as? MenuHost ?: return
|
||||
menuHost.addMenuProvider(it, viewLifecycleOwner, Lifecycle.State.RESUMED)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun loadToolbarMenu(): MenuProvider? = null
|
||||
|
||||
protected open fun configureTransitions() {
|
||||
configureDefaultTransactions()
|
||||
}
|
||||
|
||||
protected fun configureDefaultTransactions() {
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
enterTransition = inflater.inflateTransition(R.transition.fade)
|
||||
exitTransition = inflater.inflateTransition(R.transition.fade)
|
||||
}
|
||||
|
||||
fun showRetrySnackbar(message: String, action: VoidCallback) {
|
||||
val snackbar = Snackbar.make(mainView, message, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbar.setAction(getString(R.string.common_retry)) {
|
||||
snackbar.dismiss()
|
||||
action()
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
interface FragmentOnBackPressedHandler {
|
||||
fun handleOnBackPressed()
|
||||
}
|
||||
|
||||
@SuppressLint("FragmentBackPressedCallback")
|
||||
fun Fragment.addBackPressHandler(handler: FragmentOnBackPressedHandler) {
|
||||
requireActivity().onBackPressedDispatcher.addCallback(
|
||||
owner = this,
|
||||
onBackPressed = { handler.handleOnBackPressed() },
|
||||
)
|
||||
|
||||
view?.findViewById<Toolbar>(R.id.toolbar)?.setNavigationOnClickListener {
|
||||
handler.handleOnBackPressed()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.features
|
||||
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseStoreFragment(layoutId: Int) : BaseFragment(layoutId) {
|
||||
|
||||
protected val storeSubscribersList = mutableListOf<StoreSubscriber<*>>()
|
||||
|
||||
abstract fun subscribeToStore()
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
subscribeToStore()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
storeSubscribersList.forEach { store.unsubscribe(it) }
|
||||
storeSubscribersList.clear()
|
||||
super.onStop()
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -16,13 +15,8 @@ import org.rekotlin.Action
|
|||
object DemoHelper {
|
||||
val config = DemoConfig()
|
||||
|
||||
private val demoMiddlewares = listOf(
|
||||
DemoOnboardingNoteMiddleware(),
|
||||
)
|
||||
|
||||
private val disabledActionFeatures = listOf(
|
||||
WalletConnectAction.StartWalletConnect::class.java,
|
||||
BackupAction.StartBackup::class.java,
|
||||
)
|
||||
|
||||
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
|
||||
|
|
@ -35,10 +29,6 @@ object DemoHelper {
|
|||
val scanResponse = getScanResponse(appState) ?: return false
|
||||
if (!scanResponse.isDemoCard()) return false
|
||||
|
||||
demoMiddlewares.forEach {
|
||||
if (it.tryHandle(config, scanResponse, action)) return true
|
||||
}
|
||||
|
||||
disabledActionFeatures.firstOrNull { it == action::class.java }?.let {
|
||||
val uiMessageSender = store.inject(DaggerGraphState::uiMessageSender)
|
||||
uiMessageSender.send(
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package com.tangem.tap.features.demo
|
||||
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DemoOnboardingNoteMiddleware : DemoMiddleware {
|
||||
|
||||
override fun tryHandle(config: DemoConfig, scanResponse: ScanResponse, action: Action): Boolean {
|
||||
val noteState = store.state.onboardingNoteState
|
||||
|
||||
return when (action) {
|
||||
is OnboardingNoteAction.Balance.Update -> {
|
||||
val walletManager = if (noteState.walletManager != null) {
|
||||
noteState.walletManager
|
||||
} else {
|
||||
val wmFactory = runBlocking {
|
||||
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
|
||||
}
|
||||
val walletManager = wmFactory?.makePrimaryWalletManager(scanResponse).guard {
|
||||
return false
|
||||
}
|
||||
store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
|
||||
walletManager
|
||||
}
|
||||
|
||||
val balanceAmount = config.getBalance(walletManager.wallet.blockchain)
|
||||
val loadedBalance = noteState.walletBalance.copy(
|
||||
value = balanceAmount.value!!,
|
||||
currency = Currency.Blockchain(walletManager.wallet.blockchain, null),
|
||||
state = ProgressState.Done,
|
||||
error = null,
|
||||
criticalError = null,
|
||||
)
|
||||
|
||||
walletManager.wallet.setAmount(balanceAmount)
|
||||
|
||||
scope.launch {
|
||||
withMainContext {
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetCriticalError(loadedBalance.criticalError))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
|
|
@ -33,8 +32,6 @@ import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState
|
|||
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
|
||||
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
|
||||
import com.tangem.tap.features.details.ui.common.utils.*
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -56,7 +53,6 @@ internal class CardSettingsModel @Inject constructor(
|
|||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<CardSettingsComponent.Params>()
|
||||
|
|
@ -200,23 +196,13 @@ internal class CardSettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
if (scanResponse.cardTypesResolver.isTangemTwins()) {
|
||||
if (onboardingV2FeatureToggles.isTwinRefactoringEnabled) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.RecreateWalletTwin,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// needs to prepare twin state if it's twin cards (depends on onboarding refactoring)
|
||||
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
|
||||
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse)))
|
||||
|
||||
cardSettingsInteractor.clear()
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) }
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.RecreateWalletTwin,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val card = scanResponse.card
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.coil.ImagePreloader
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
|
@ -21,6 +22,7 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
|
|||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.domain.onramp.FetchHotCryptoUseCase
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
|
|
@ -30,7 +32,11 @@ import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
|||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||
import com.tangem.features.onramp.deeplink.OnrampDeepLink
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -58,6 +64,9 @@ internal class MainViewModel @Inject constructor(
|
|||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val imagePreloader: ImagePreloader,
|
||||
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
private val onrampDeepLinkFactory: OnrampDeepLink.Factory,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -92,6 +101,15 @@ internal class MainViewModel @Inject constructor(
|
|||
sendKeyboardIdentifierEvent()
|
||||
|
||||
preloadImages()
|
||||
|
||||
initializeDeepLinks()
|
||||
}
|
||||
|
||||
fun checkForUnfinishedBackup() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
|
||||
}
|
||||
}
|
||||
|
||||
/** Loading the resources needed to run the application */
|
||||
|
|
@ -321,4 +339,8 @@ internal class MainViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeDeepLinks() {
|
||||
deepLinksRegistry.register(onrampDeepLinkFactory.create(viewModelScope))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingDialog : StateDialog {
|
||||
object TwinningProcessNotCompleted : OnboardingDialog()
|
||||
data class InterruptOnboarding(val onOk: VoidCallback) : OnboardingDialog()
|
||||
data class WalletActivationError(val onConfirm: () -> Unit) : OnboardingDialog()
|
||||
}
|
||||
|
|
@ -1,46 +1,23 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object OnboardingHelper {
|
||||
suspend fun isOnboardingCase(response: ScanResponse): Boolean {
|
||||
val onboardingManager =
|
||||
store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response)
|
||||
val cardRepository = store.inject(DaggerGraphState::cardRepository)
|
||||
val cardId = response.card.cardId
|
||||
|
||||
return when {
|
||||
response.cardTypesResolver.isVisaWallet() -> {
|
||||
if (response.visaCardActivationStatus == null) error("Visa card activation status is null")
|
||||
|
|
@ -52,227 +29,20 @@ object OnboardingHelper {
|
|||
if (!response.twinsIsTwinned()) {
|
||||
true
|
||||
} else {
|
||||
onboardingManager.isActivationInProgress(cardId)
|
||||
cardRepository.isActivationInProgress(cardId)
|
||||
}
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> {
|
||||
val emptyWallets = response.card.wallets.isEmpty()
|
||||
val activationInProgress = onboardingManager.isActivationInProgress(cardId)
|
||||
val activationInProgress = cardRepository.isActivationInProgress(cardId)
|
||||
val isNoBackup = response.card.backupStatus == CardDTO.BackupStatus.NoBackup &&
|
||||
!DemoHelper.isDemoCard(response)
|
||||
emptyWallets || activationInProgress || isNoBackup
|
||||
}
|
||||
|
||||
response.card.wallets.isNotEmpty() -> onboardingManager.isActivationInProgress(cardId)
|
||||
response.card.wallets.isNotEmpty() -> cardRepository.isActivationInProgress(cardId)
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||
val newOnboardingSupportTypes = scanResponse.productType == ProductType.Wallet2 ||
|
||||
scanResponse.productType == ProductType.Ring ||
|
||||
scanResponse.productType == ProductType.Wallet ||
|
||||
scanResponse.productType == ProductType.Visa // AppRoute.OnboardingOther is also supported
|
||||
val featureToggles = store.inject(DaggerGraphState::onboardingV2FeatureToggles)
|
||||
if (featureToggles.isOnboardingV2Enabled &&
|
||||
newOnboardingSupportTypes
|
||||
) {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
|
||||
return AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
mode = when (backupState.startedSource) {
|
||||
BackupStartedSource.Onboarding -> AppRoute.Onboarding.Mode.Onboarding
|
||||
BackupStartedSource.CreateBackup -> AppRoute.Onboarding.Mode.AddBackupWallet1
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return when (val type = scanResponse.productType) {
|
||||
ProductType.Note -> if (featureToggles.isNoteRefactoringEnabled) {
|
||||
AppRoute.Onboarding(scanResponse = scanResponse)
|
||||
} else {
|
||||
AppRoute.OnboardingNote
|
||||
}
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
ProductType.Ring,
|
||||
-> if (scanResponse.card.settings.isBackupAllowed) {
|
||||
AppRoute.OnboardingWallet()
|
||||
} else {
|
||||
AppRoute.OnboardingOther
|
||||
}
|
||||
ProductType.Twins -> if (featureToggles.isTwinRefactoringEnabled) {
|
||||
AppRoute.Onboarding(scanResponse = scanResponse)
|
||||
} else {
|
||||
AppRoute.OnboardingTwins
|
||||
}
|
||||
ProductType.Start2Coin,
|
||||
ProductType.Visa,
|
||||
-> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveWallet(
|
||||
alreadyCreatedWallet: UserWallet?,
|
||||
scanResponse: ScanResponse,
|
||||
accessCode: String? = null,
|
||||
backupCardsIds: List<String>? = null,
|
||||
hasBackupError: Boolean = false,
|
||||
) {
|
||||
Analytics.setContext(scanResponse)
|
||||
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
|
||||
when {
|
||||
// When should save user wallets, then save card without navigate to save wallet screen
|
||||
store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.SaveWalletAfterBackup(
|
||||
hasBackupError = hasBackupError,
|
||||
shouldNavigateToWallet = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
||||
// then open save wallet screen
|
||||
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
|
||||
delay(timeMillis = 1_200)
|
||||
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
}
|
||||
}
|
||||
|
||||
fun trySaveWalletAndNavigateToWalletScreen(
|
||||
scanResponse: ScanResponse,
|
||||
accessCode: String? = null,
|
||||
backupCardsIds: List<String>? = null,
|
||||
hasBackupError: Boolean = false,
|
||||
) {
|
||||
Analytics.setContext(scanResponse)
|
||||
scope.launch {
|
||||
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
|
||||
|
||||
when {
|
||||
// When should save user wallets, then save card without navigate to save wallet screen
|
||||
store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.SaveWalletAfterBackup(
|
||||
hasBackupError = hasBackupError,
|
||||
shouldNavigateToWallet = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
||||
// then open save wallet screen
|
||||
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
|
||||
delay(timeMillis = 1_200)
|
||||
|
||||
store.dispatchOnMain(
|
||||
SaveWalletAction.ProvideBackupInfo(
|
||||
scanResponse = scanResponse,
|
||||
accessCode = accessCode,
|
||||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
delay(timeMillis = 1_800)
|
||||
store.dispatchNavigationAction { push(AppRoute.SaveWallet) }
|
||||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onInterrupted() {
|
||||
Analytics.removeContext()
|
||||
}
|
||||
|
||||
fun sendToppedUpEvent(scanResponse: ScanResponse) {
|
||||
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
val currency = ParamCardCurrencyConverter().convert(scanResponse.cardTypesResolver)
|
||||
|
||||
if (userWalletId != null && currency != null) {
|
||||
Analytics.send(Basic.ToppedUp(userWalletId.stringValue, currency))
|
||||
}
|
||||
}
|
||||
|
||||
fun handleTopUpAction(walletManager: WalletManager, scanResponse: ScanResponse) {
|
||||
val blockchain = walletManager.wallet.blockchain
|
||||
val excludedBlockchains = store.inject(DaggerGraphState::excludedBlockchains)
|
||||
|
||||
val cryptoCurrency = CryptoCurrencyFactory(excludedBlockchains).createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
) ?: return
|
||||
|
||||
val topUpUrl = walletManager.getTopUpUrl(cryptoCurrency) ?: return
|
||||
|
||||
val currencyType = AnalyticsParam.CurrencyType.Blockchain(blockchain)
|
||||
Analytics.send(Onboarding.Topup.ButtonBuyCrypto(currencyType))
|
||||
|
||||
store.dispatchOpenUrl(topUpUrl)
|
||||
}
|
||||
|
||||
private suspend fun proceedWithScanResponse(
|
||||
scanResponse: ScanResponse,
|
||||
backupCardsIds: List<String>?,
|
||||
hasBackupError: Boolean,
|
||||
alreadyCreatedWallet: UserWallet? = null,
|
||||
) {
|
||||
val userWalletBuilderFactory = store.inject(DaggerGraphState::userWalletBuilderFactory)
|
||||
|
||||
val userWallet = alreadyCreatedWallet ?: userWalletBuilderFactory.create(scanResponse)
|
||||
.hasBackupError(hasBackupError)
|
||||
.backupCardsIds(backupCardsIds?.toSet())
|
||||
.build()
|
||||
.guard {
|
||||
Timber.e("User wallet not created")
|
||||
return
|
||||
}
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
.doOnSuccess {
|
||||
mainScope.launch { store.onUserWalletSelected(userWallet) }
|
||||
store.dispatchWithMain(OnboardingWalletAction.WalletSaved(userWallet.walletId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,141 +1,6 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.domain.card.repository.CardRepository
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.attestation.api.models.CardVerifyAndGetInfo
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.safeUpdate
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.model.hasPendingTransactions
|
||||
import com.tangem.tap.features.demo.isDemoCard
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Deprecated("Remove when navigation refactoring will be implemented")
|
||||
class OnboardingManager(var scanResponse: ScanResponse) {
|
||||
|
||||
private val cardRepository: CardRepository by lazy { store.inject(DaggerGraphState::cardRepository) }
|
||||
|
||||
private val onlineCardVerifier: OnlineCardVerifier by lazy { store.inject(DaggerGraphState::onlineCardVerifier) }
|
||||
|
||||
private var cardInfo: Result<CardVerifyAndGetInfo.Response.Item>? = null
|
||||
|
||||
suspend fun loadArtworkUrl(): String {
|
||||
val cardInfo = cardInfo
|
||||
?: onlineCardVerifier.getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey)
|
||||
|
||||
this.cardInfo = cardInfo
|
||||
|
||||
return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo, onlineCardVerifier)
|
||||
}
|
||||
|
||||
suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance {
|
||||
val isDemoCard = scanResponse.isDemoCard()
|
||||
val balance = when (val result = walletManager.safeUpdate(isDemoCard)) {
|
||||
is Result.Success -> {
|
||||
val wallet = walletManager.wallet
|
||||
val valueOfAmount = wallet.amounts[AmountType.Coin]?.value
|
||||
if (valueOfAmount == null) {
|
||||
val customError = TapError.CustomError("Amount is NULL")
|
||||
OnboardingWalletBalance.criticalError(customError)
|
||||
} else {
|
||||
val balance = if (valueOfAmount.isZero()) {
|
||||
OnboardingWalletBalance.done(valueOfAmount, wallet.hasPendingTransactions())
|
||||
} else {
|
||||
OnboardingWalletBalance.done(valueOfAmount, false)
|
||||
}
|
||||
balance
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
val error = result.error as? TapError ?: TapError.UnknownError
|
||||
when (error) {
|
||||
is TapError.WalletManager.NoAccountError -> OnboardingWalletBalance.error(error)
|
||||
// NoInternetConnection, WalletManager.InternalError
|
||||
else -> {
|
||||
Timber.e(error.localizedMessage)
|
||||
OnboardingWalletBalance.criticalError(TapError.WalletManager.BlockchainIsUnreachableTryLater)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return balance.copy(
|
||||
currency = Currency.Blockchain(
|
||||
blockchain = walletManager.wallet.blockchain,
|
||||
derivationPath = walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun startActivation(cardId: String) {
|
||||
cardRepository.startCardActivation(cardId)
|
||||
}
|
||||
|
||||
suspend fun finishActivation(cardId: String) {
|
||||
cardRepository.finishCardActivation(cardId)
|
||||
}
|
||||
|
||||
suspend fun finishActivation(cardIds: List<String>) {
|
||||
cardRepository.finishCardsActivation(cardIds)
|
||||
}
|
||||
|
||||
suspend fun isActivationStarted(cardId: String): Boolean = cardRepository.isActivationStarted(cardId)
|
||||
|
||||
suspend fun isActivationFinished(cardId: String): Boolean = cardRepository.isActivationFinished(cardId)
|
||||
|
||||
suspend fun isActivationInProgress(cardId: String): Boolean = cardRepository.isActivationInProgress(cardId)
|
||||
}
|
||||
|
||||
data class OnboardingWalletBalance(
|
||||
val value: BigDecimal = BigDecimal.ZERO,
|
||||
val currency: Currency = Currency.Blockchain(Blockchain.Unknown, null),
|
||||
val hasIncomingTransaction: Boolean = false,
|
||||
val state: ProgressState,
|
||||
val error: TapError? = null,
|
||||
val criticalError: TapError? = null,
|
||||
) {
|
||||
|
||||
val amountToCreateAccount: String?
|
||||
get() = if (error is TapError.WalletManager.NoAccountError) error.customMessage else null
|
||||
|
||||
fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction
|
||||
|
||||
companion object {
|
||||
fun error(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
state = ProgressState.Error,
|
||||
error = error,
|
||||
)
|
||||
|
||||
fun criticalError(error: TapError): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
state = ProgressState.Error,
|
||||
criticalError = error,
|
||||
)
|
||||
|
||||
fun loading(value: BigDecimal = BigDecimal.ZERO): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
value,
|
||||
state = ProgressState.Loading,
|
||||
)
|
||||
|
||||
fun done(value: BigDecimal, hasTransactions: Boolean): OnboardingWalletBalance = OnboardingWalletBalance(
|
||||
value,
|
||||
hasIncomingTransaction = hasTransactions,
|
||||
state = ProgressState.Done,
|
||||
)
|
||||
}
|
||||
}
|
||||
class OnboardingManager(var scanResponse: ScanResponse)
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingMenuProvider(
|
||||
private val scanResponseProvider: Provider<ScanResponse>,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_onboarding, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.menu_item_chat_support -> {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
|
||||
val cardInfo = store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponseProvider()).getOrNull()
|
||||
?: error("CardInfo must be not null")
|
||||
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||
.invoke(type = FeedbackEmailType.DirectUserRequest(cardInfo))
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.MenuProvider
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.transitions.HomeToOnboardingTransition
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingMainBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
import org.rekotlin.StoreSubscriber
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseOnboardingFragment<T> : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber<T> {
|
||||
|
||||
protected val binding: FragmentOnboardingMainBinding by viewBinding(FragmentOnboardingMainBinding::bind)
|
||||
protected val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
|
||||
|
||||
override fun configureTransitions() {
|
||||
enterTransition = HomeToOnboardingTransition()
|
||||
exitTransition = HomeToOnboardingTransition()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: store.state.detailsState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
protected fun showConfetti(show: Boolean) = with(binding.vConfetti) {
|
||||
lavConfetti.show(show)
|
||||
|
||||
if (show) {
|
||||
lavConfetti.playAnimation()
|
||||
} else {
|
||||
lavConfetti.cancelAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,228 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.ShareElement
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteStep
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Deprecated("Use OnboardingNoteComponent")
|
||||
class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
||||
|
||||
private var previousStep: OnboardingNoteStep = OnboardingNoteStep.None
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
|
||||
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
|
||||
binding.toolbar.setTitle(R.string.onboarding_title)
|
||||
btnRefreshBalanceWidget = RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer)
|
||||
|
||||
store.dispatch(OnboardingNoteAction.Init)
|
||||
store.dispatch(OnboardingNoteAction.LoadCardArtwork)
|
||||
store.dispatch(OnboardingNoteAction.DetermineStepOfScreen)
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.onboardingNoteState == newState.onboardingNoteState
|
||||
}.select { it.onboardingNoteState }
|
||||
}
|
||||
storeSubscribersList.add(this)
|
||||
}
|
||||
|
||||
override fun newState(state: OnboardingNoteState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
|
||||
pbBinding.pbState.max = state.steps.size - 1
|
||||
pbBinding.pbState.progress = state.progress
|
||||
|
||||
when (state.currentStep) {
|
||||
OnboardingNoteStep.None -> setupNoneState()
|
||||
OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state)
|
||||
OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state)
|
||||
OnboardingNoteStep.Done -> setupDoneState(state)
|
||||
}
|
||||
setBalance(state)
|
||||
showConfetti(state.showConfetti)
|
||||
previousStep = state.currentStep
|
||||
}
|
||||
|
||||
private fun setBalance(state: OnboardingNoteState) {
|
||||
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
|
||||
|
||||
with(mainBinding.onboardingTopContainer.onboardingTvBalance) {
|
||||
if (state.balanceCriticalError == null) {
|
||||
val balanceValue = state.walletBalance.value.stripZeroPlainString()
|
||||
tvBalanceValue.text = balanceValue
|
||||
tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
|
||||
} else {
|
||||
tvBalanceValue.text = "–"
|
||||
tvBalanceCurrency.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupNoneState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.isVisible = false
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnRefreshBalanceWidget.show(false)
|
||||
tvHeader.isVisible = false
|
||||
tvBody.isVisible = false
|
||||
|
||||
btnMainAction.text = ""
|
||||
btnMainAction.icon = null
|
||||
btnAlternativeAction.text = ""
|
||||
btnAlternativeAction.icon = null
|
||||
tvHeader.text = ""
|
||||
tvBody.text = ""
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingNoteAction.CreateWallet)
|
||||
}
|
||||
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
|
||||
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_circle),
|
||||
)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet)
|
||||
|
||||
btnAlternativeAction.isVisible = false // temporary
|
||||
}
|
||||
|
||||
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
if (availableForBuy(state.scanResponse, state.walletBalance)) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.TopUp)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = true
|
||||
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
|
||||
btnAlternativeAction.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
|
||||
}
|
||||
} else {
|
||||
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
}
|
||||
|
||||
tvHeader.setText(R.string.onboarding_topup_title)
|
||||
if (state.balanceNonCriticalError == null) {
|
||||
tvBody.setText(R.string.onboarding_top_up_body)
|
||||
} else {
|
||||
state.walletBalance.amountToCreateAccount?.let { amount ->
|
||||
val tvBodyMessage = getString(
|
||||
R.string.onboarding_top_up_body_no_account_error,
|
||||
amount,
|
||||
state.walletBalance.currency.currencySymbol,
|
||||
)
|
||||
tvBody.text = tvBodyMessage
|
||||
}
|
||||
}
|
||||
|
||||
btnRefreshBalanceWidget.changeState(state.walletBalance.state)
|
||||
if (btnRefreshBalanceWidget.isShowing != true) {
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener {
|
||||
store.dispatch(OnboardingNoteAction.Balance.Update)
|
||||
}
|
||||
}
|
||||
|
||||
val drawable = requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(drawable)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet)
|
||||
}
|
||||
|
||||
private fun setupDoneState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingNoteAction.Done)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.text = ""
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener(null)
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation)
|
||||
}
|
||||
|
||||
private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) {
|
||||
if (this.previousStep == currentStep) return
|
||||
|
||||
with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean {
|
||||
scanResponse ?: return false
|
||||
return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency)
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(OnboardingNoteAction.OnBackPressed)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingNoteAction : Action {
|
||||
// from user, ui
|
||||
object Init : OnboardingNoteAction()
|
||||
object LoadCardArtwork : OnboardingNoteAction()
|
||||
object CreateWallet : OnboardingNoteAction()
|
||||
object TopUp : OnboardingNoteAction()
|
||||
object ShowAddressInfoDialog : OnboardingNoteAction()
|
||||
object OnBackPressed : OnboardingNoteAction()
|
||||
|
||||
// from redux
|
||||
class SetArtworkUrl(val artworkUrl: String) : OnboardingNoteAction()
|
||||
data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction()
|
||||
object DetermineStepOfScreen : OnboardingNoteAction()
|
||||
object Done : OnboardingNoteAction()
|
||||
|
||||
data class SetStepOfScreen(val step: OnboardingNoteStep) : OnboardingNoteAction()
|
||||
|
||||
sealed class Balance {
|
||||
object Update : OnboardingNoteAction()
|
||||
data class Set(val balance: OnboardingWalletBalance) : OnboardingNoteAction()
|
||||
data class SetCriticalError(val error: TapError?) : OnboardingNoteAction()
|
||||
data class SetNonCriticalError(val error: TapError?) : OnboardingNoteAction()
|
||||
}
|
||||
|
||||
sealed class Confetti {
|
||||
object Show : OnboardingNoteAction()
|
||||
object Hide : OnboardingNoteAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
object OnboardingNoteMiddleware {
|
||||
val handler = onboardingNoteMiddleware
|
||||
}
|
||||
|
||||
private val onboardingNoteMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleNoteAction(state, action, dispatch)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch: DispatchFunction) {
|
||||
if (action !is OnboardingNoteAction) return
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager ?: return
|
||||
|
||||
val scanResponse = onboardingManager.scanResponse
|
||||
val card = onboardingManager.scanResponse.card
|
||||
val noteState = store.state.onboardingNoteState
|
||||
|
||||
when (action) {
|
||||
is OnboardingNoteAction.Init -> {
|
||||
scope.launch {
|
||||
if (!onboardingManager.isActivationStarted(card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.LoadCardArtwork -> {
|
||||
scope.launch {
|
||||
val artworkUrl = onboardingManager.loadArtworkUrl()
|
||||
withMainContext { store.dispatch(OnboardingNoteAction.SetArtworkUrl(artworkUrl)) }
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.DetermineStepOfScreen -> {
|
||||
val step = when {
|
||||
card.wallets.isEmpty() -> OnboardingNoteStep.CreateWallet
|
||||
noteState.walletBalance.balanceIsToppedUp() -> OnboardingNoteStep.Done
|
||||
else -> OnboardingNoteStep.TopUpWallet
|
||||
}
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(step))
|
||||
}
|
||||
is OnboardingNoteAction.SetStepOfScreen -> {
|
||||
when (action.step) {
|
||||
OnboardingNoteStep.CreateWallet -> {
|
||||
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
|
||||
}
|
||||
OnboardingNoteStep.TopUpWallet -> {
|
||||
Analytics.send(Onboarding.Topup.ScreenOpened())
|
||||
store.dispatch(OnboardingNoteAction.Balance.Update)
|
||||
}
|
||||
OnboardingNoteStep.Done -> {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
mainScope.launch {
|
||||
onboardingManager.finishActivation(card.cardId)
|
||||
postUi(DELAY_SDK_DIALOG_CLOSE) { store.dispatch(OnboardingNoteAction.Confetti.Show) }
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createProductWallet(scanResponse)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
val updatedResponse = scanResponse.copy(card = result.data.card)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.startActivation(updatedResponse.card.cardId)
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet))
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.Balance.Update -> {
|
||||
val walletManager = if (noteState.walletManager != null) {
|
||||
noteState.walletManager
|
||||
} else {
|
||||
val wmFactory = runBlocking {
|
||||
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
|
||||
}
|
||||
val walletManager = wmFactory?.makePrimaryWalletManager(scanResponse).guard {
|
||||
val message = "Loading cancelled. Cause: wallet manager didn't created"
|
||||
val customError = TapError.CustomError(message)
|
||||
store.dispatchErrorNotification(customError)
|
||||
return
|
||||
}
|
||||
dispatch(OnboardingNoteAction.SetWalletManager(walletManager))
|
||||
walletManager
|
||||
}
|
||||
|
||||
val isLoadedBefore = noteState.walletBalance.state != ProgressState.Loading
|
||||
val balanceIsLoading = noteState.walletBalance.copy(
|
||||
currency = Currency.Blockchain(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
criticalError = null,
|
||||
)
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading))
|
||||
|
||||
scope.launch {
|
||||
val loadedBalance = onboardingManager.updateBalance(walletManager)
|
||||
delay(if (isLoadedBefore) 0 else 300)
|
||||
loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) }
|
||||
withMainContext {
|
||||
store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetCriticalError(loadedBalance.criticalError))
|
||||
store.dispatch(OnboardingNoteAction.Balance.SetNonCriticalError(loadedBalance.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.Balance.Set -> {
|
||||
if (action.balance.balanceIsToppedUp()) {
|
||||
OnboardingHelper.sendToppedUpEvent(scanResponse)
|
||||
store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done))
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.ShowAddressInfoDialog -> {
|
||||
val addressData = noteState.walletManager?.getAddressData() ?: return
|
||||
|
||||
Analytics.send(Onboarding.Topup.ButtonShowWalletAddress())
|
||||
val appDialog = AppDialog.AddressInfoDialog(noteState.walletBalance.currency, addressData)
|
||||
store.dispatchDialogShow(appDialog)
|
||||
}
|
||||
is OnboardingNoteAction.TopUp -> {
|
||||
val walletManager = noteState.walletManager.guard {
|
||||
store.dispatchDebugErrorNotification("NPE: WalletManager")
|
||||
return
|
||||
}
|
||||
|
||||
OnboardingHelper.handleTopUpAction(
|
||||
walletManager = walletManager,
|
||||
scanResponse = scanResponse,
|
||||
)
|
||||
}
|
||||
is OnboardingNoteAction.Done -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
}
|
||||
is OnboardingNoteAction.OnBackPressed -> {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.InterruptOnboarding(
|
||||
onOk = {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import org.rekotlin.Action
|
||||
|
||||
object OnboardingNoteReducer {
|
||||
fun reduce(action: Action, state: AppState): OnboardingNoteState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, appState: AppState): OnboardingNoteState {
|
||||
var state = appState.onboardingNoteState
|
||||
|
||||
when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
state = OnboardingNoteState(scanResponse = action.scanResponse)
|
||||
}
|
||||
is OnboardingNoteAction.SetArtworkUrl -> {
|
||||
state = state.copy(cardArtworkUrl = action.artworkUrl)
|
||||
}
|
||||
is OnboardingNoteAction.SetWalletManager -> {
|
||||
state = state.copy(walletManager = action.walletManager)
|
||||
}
|
||||
is OnboardingNoteAction.Balance.Set -> {
|
||||
state = state.copy(walletBalance = action.balance)
|
||||
}
|
||||
is OnboardingNoteAction.Balance.SetCriticalError -> {
|
||||
state = state.copy(balanceCriticalError = action.error)
|
||||
}
|
||||
is OnboardingNoteAction.Balance.SetNonCriticalError -> {
|
||||
state = state.copy(balanceNonCriticalError = action.error)
|
||||
}
|
||||
is OnboardingNoteAction.SetStepOfScreen -> {
|
||||
if (action.step != state.currentStep && state.steps.contains(action.step)) {
|
||||
state = state.copy(currentStep = action.step)
|
||||
}
|
||||
}
|
||||
is OnboardingNoteAction.Confetti.Show -> {
|
||||
state = state.copy(showConfetti = true)
|
||||
}
|
||||
is OnboardingNoteAction.Confetti.Hide -> {
|
||||
state = state.copy(showConfetti = false)
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingNoteState(
|
||||
val walletManager: WalletManager? = null,
|
||||
// UI
|
||||
val cardArtworkUrl: String? = null,
|
||||
val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(),
|
||||
val balanceNonCriticalError: TapError? = null,
|
||||
val balanceCriticalError: TapError? = null,
|
||||
val currentStep: OnboardingNoteStep = OnboardingNoteStep.None,
|
||||
val steps: List<OnboardingNoteStep> = OnboardingNoteStep.values().toList(),
|
||||
val showConfetti: Boolean = false,
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : StateType {
|
||||
|
||||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
}
|
||||
|
||||
enum class OnboardingNoteStep {
|
||||
None, CreateWallet, TopUpWallet, Done
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.ShareElement
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState
|
||||
import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsStep
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingOtherCardsFragment : BaseOnboardingFragment<OnboardingOtherCardsState>() {
|
||||
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
|
||||
binding.toolbar.setTitle(R.string.onboarding_title)
|
||||
store.dispatch(OnboardingOtherCardsAction.Init)
|
||||
store.dispatch(OnboardingOtherCardsAction.LoadCardArtwork)
|
||||
store.dispatch(OnboardingOtherCardsAction.DetermineStepOfScreen)
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.onboardingOtherCardsState == newState.onboardingOtherCardsState
|
||||
}.select { it.onboardingOtherCardsState }
|
||||
}
|
||||
storeSubscribersList.add(this)
|
||||
}
|
||||
|
||||
override fun newState(state: OnboardingOtherCardsState) {
|
||||
if (activity == null || view == null) return
|
||||
if (state.currentStep == OnboardingOtherCardsStep.None) return
|
||||
|
||||
mainBinding.onboardingTopContainer.imvFrontCard.load(state.cardArtworkUrl) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
|
||||
pbBinding.pbState.max = state.steps.size - 1
|
||||
pbBinding.pbState.progress = state.progress
|
||||
|
||||
when (state.currentStep) {
|
||||
OnboardingOtherCardsStep.CreateWallet -> setupCreateWalletState()
|
||||
OnboardingOtherCardsStep.Done -> setupDoneState()
|
||||
else -> {}
|
||||
}
|
||||
showConfetti(state.showConfetti)
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingOtherCardsAction.CreateWallet)
|
||||
}
|
||||
btnAlternativeAction.setText(R.string.onboarding_button_what_does_it_mean)
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
btnAlternativeAction.isVisible = false // temporary
|
||||
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_circle),
|
||||
)
|
||||
updateConstraints(R.layout.lp_onboarding_create_wallet)
|
||||
}
|
||||
|
||||
private fun setupDoneState() = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingOtherCardsAction.Done)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.text = ""
|
||||
btnAlternativeAction.setOnClickListener { }
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
updateConstraints(R.layout.lp_onboarding_done)
|
||||
}
|
||||
|
||||
private fun updateConstraints(@LayoutRes layoutId: Int) = with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(OnboardingOtherCardsAction.OnBackPressed)
|
||||
super.handleOnBackPressed()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class OnboardingOtherCardsAction : Action {
|
||||
// from user, ui
|
||||
object Init : OnboardingOtherCardsAction()
|
||||
object LoadCardArtwork : OnboardingOtherCardsAction()
|
||||
object CreateWallet : OnboardingOtherCardsAction()
|
||||
|
||||
// from redux
|
||||
class SetArtworkUrl(val artworkUrl: String) : OnboardingOtherCardsAction()
|
||||
object DetermineStepOfScreen : OnboardingOtherCardsAction()
|
||||
object Done : OnboardingOtherCardsAction()
|
||||
object OnBackPressed : OnboardingOtherCardsAction()
|
||||
|
||||
data class SetStepOfScreen(val step: OnboardingOtherCardsStep) : OnboardingOtherCardsAction()
|
||||
|
||||
sealed class Confetti {
|
||||
object Show : OnboardingOtherCardsAction()
|
||||
object Hide : OnboardingOtherCardsAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
object OnboardingOtherCardsMiddleware {
|
||||
val handler = onboardingOtherCardsMiddleware
|
||||
}
|
||||
|
||||
private val onboardingOtherCardsMiddleware: Middleware<AppState> = { dispatch, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handleOtherCardsAction(action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleOtherCardsAction(action: Action) {
|
||||
if (action !is OnboardingOtherCardsAction) return
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager ?: return
|
||||
|
||||
val card = onboardingManager.scanResponse.card
|
||||
|
||||
when (action) {
|
||||
is OnboardingOtherCardsAction.Init -> {
|
||||
scope.launch {
|
||||
if (!onboardingManager.isActivationStarted(card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.LoadCardArtwork -> {
|
||||
scope.launch {
|
||||
val artworkUrl = onboardingManager.loadArtworkUrl()
|
||||
withMainContext { store.dispatch(OnboardingOtherCardsAction.SetArtworkUrl(artworkUrl)) }
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.DetermineStepOfScreen -> {
|
||||
val step = when {
|
||||
card.wallets.isEmpty() -> OnboardingOtherCardsStep.CreateWallet
|
||||
else -> OnboardingOtherCardsStep.Done
|
||||
}
|
||||
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(step))
|
||||
}
|
||||
is OnboardingOtherCardsAction.SetStepOfScreen -> {
|
||||
when (action.step) {
|
||||
OnboardingOtherCardsStep.CreateWallet -> {
|
||||
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
|
||||
}
|
||||
OnboardingOtherCardsStep.Done -> {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
mainScope.launch {
|
||||
onboardingManager.finishActivation(card.cardId)
|
||||
postUi(200) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) }
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.CreateWallet -> {
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createProductWallet(
|
||||
scanResponse = onboardingManager.scanResponse,
|
||||
shouldReset = globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
withMainContext {
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
val updatedResponse = onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
)
|
||||
val updatedCard = updatedResponse.card
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
onboardingManager.startActivation(updatedCard.cardId)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OnboardingOtherCardsAction.Done -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(onboardingManager.scanResponse)
|
||||
}
|
||||
OnboardingOtherCardsAction.OnBackPressed -> {
|
||||
OnboardingHelper.onInterrupted()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleActivationError() {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.WalletActivationError(
|
||||
onConfirm = {
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(true))
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import org.rekotlin.Action
|
||||
|
||||
object OnboardingOtherCardsReducer {
|
||||
fun reduce(action: Action, state: AppState): OnboardingOtherCardsState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
private fun internalReduce(action: Action, appState: AppState): OnboardingOtherCardsState {
|
||||
var state = appState.onboardingOtherCardsState
|
||||
|
||||
when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
state = OnboardingOtherCardsState()
|
||||
}
|
||||
is OnboardingOtherCardsAction.SetArtworkUrl -> {
|
||||
state = state.copy(cardArtworkUrl = action.artworkUrl)
|
||||
}
|
||||
is OnboardingOtherCardsAction.SetStepOfScreen -> {
|
||||
if (action.step != state.currentStep && state.steps.contains(action.step)) {
|
||||
state = state.copy(currentStep = action.step)
|
||||
}
|
||||
}
|
||||
is OnboardingOtherCardsAction.Confetti.Show -> {
|
||||
state = state.copy(showConfetti = true)
|
||||
}
|
||||
is OnboardingOtherCardsAction.Confetti.Hide -> {
|
||||
state = state.copy(showConfetti = false)
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.otherCards.redux
|
||||
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingOtherCardsState(
|
||||
// UI
|
||||
val cardArtworkUrl: String? = null,
|
||||
val showConfetti: Boolean = false,
|
||||
val currentStep: OnboardingOtherCardsStep = OnboardingOtherCardsStep.None,
|
||||
val steps: List<OnboardingOtherCardsStep> = OnboardingOtherCardsStep.values().toList(),
|
||||
) : StateType {
|
||||
|
||||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
}
|
||||
|
||||
enum class OnboardingOtherCardsStep {
|
||||
None, CreateWallet, Done
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import org.rekotlin.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class TwinCardsAction : Action {
|
||||
data class IfTwinsPrepareState(val scanResponse: ScanResponse) : TwinCardsAction()
|
||||
|
||||
object Init : TwinCardsAction()
|
||||
data class SetMode(val mode: CreateTwinWalletMode) : TwinCardsAction()
|
||||
data class SetStepOfScreen(val step: TwinCardsStep) : TwinCardsAction()
|
||||
data class SetUserUnderstand(val isUnderstand: Boolean) : TwinCardsAction()
|
||||
|
||||
sealed class CardsManager {
|
||||
data class Set(val manager: TwinCardsManager) : TwinCardsAction()
|
||||
object Release : TwinCardsAction()
|
||||
}
|
||||
|
||||
sealed class Wallet : TwinCardsAction() {
|
||||
data class LaunchFirstStep(val initialMessage: Message) : TwinCardsAction()
|
||||
data class LaunchSecondStep(
|
||||
val initialMessage: Message,
|
||||
val preparingMessage: Message,
|
||||
val creatingWalletMessage: Message,
|
||||
) : TwinCardsAction()
|
||||
|
||||
data class LaunchThirdStep(val message: Message) : TwinCardsAction()
|
||||
}
|
||||
|
||||
// for the onboarding
|
||||
data class SetPairCardId(val cardId: String) : TwinCardsAction()
|
||||
object TopUp : TwinCardsAction()
|
||||
object ShowAddressInfoDialog : TwinCardsAction()
|
||||
data class SetWalletManager(val walletManager: WalletManager) : TwinCardsAction()
|
||||
object Done : TwinCardsAction()
|
||||
|
||||
data class OnBackPressed(
|
||||
// this is necessary for the correct animation of the return of cards to the Home screen
|
||||
val shouldResetTwinCardsWidget: (should: Boolean, popAction: VoidCallback) -> Unit,
|
||||
) : TwinCardsAction()
|
||||
|
||||
data class SaveScannedTwinCardAndNavigateToWallet(
|
||||
val scanResponse: ScanResponse,
|
||||
) : TwinCardsAction()
|
||||
|
||||
sealed class Balance {
|
||||
object Update : TwinCardsAction()
|
||||
data class Set(val balance: OnboardingWalletBalance) : TwinCardsAction()
|
||||
data class SetCriticalError(val error: TapError?) : TwinCardsAction()
|
||||
data class SetNonCriticalError(val error: TapError?) : TwinCardsAction()
|
||||
}
|
||||
|
||||
sealed class Confetti {
|
||||
object Show : TwinCardsAction()
|
||||
object Hide : TwinCardsAction()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,399 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.postUi
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
import org.rekotlin.Middleware
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object TwinCardsMiddleware {
|
||||
val handler = twinsWalletMiddleware
|
||||
}
|
||||
|
||||
private val twinsWalletMiddleware: Middleware<AppState> = { dispatch, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handle(action, dispatch)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handle(action: Action, dispatch: DispatchFunction) {
|
||||
if (action !is TwinCardsAction) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
val twinCardsState = store.state.twinCardsState
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
fun getScanResponse(): ScanResponse {
|
||||
return when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse
|
||||
is CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse
|
||||
} ?: throw NullPointerException("ScanResponse can't be NULL")
|
||||
}
|
||||
|
||||
fun updateScanResponse(response: ScanResponse) {
|
||||
when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
|
||||
is CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response))
|
||||
}
|
||||
}
|
||||
|
||||
fun startCardActivation(cardId: String) {
|
||||
if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
|
||||
scope.launch {
|
||||
onboardingManager?.startActivation(cardId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun finishCardActivation() {
|
||||
if (twinCardsState.mode == CreateTwinWalletMode.CreateWallet) {
|
||||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
val cardIds = listOfNotNull(getScanResponse().card.cardId, twinCardsState.pairCardId)
|
||||
|
||||
if (cardIds.isNotEmpty()) {
|
||||
mainScope.launch {
|
||||
onboardingManager?.finishActivation(cardIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (action) {
|
||||
is TwinCardsAction.Init -> {
|
||||
mainScope.launch {
|
||||
if (twinCardsState.currentStep is TwinCardsStep.WelcomeOnly) return@launch
|
||||
|
||||
if (twinCardsState.mode is CreateTwinWalletMode.RecreateWallet) {
|
||||
store.dispatch(GlobalAction.SaveScanResponse(twinCardsState.mode.scanResponse))
|
||||
}
|
||||
|
||||
val scanResponse = getScanResponse()
|
||||
onboardingManager?.apply {
|
||||
if (!isActivationStarted(scanResponse.card.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
|
||||
when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
mainScope.launch {
|
||||
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
|
||||
.invokeSync()
|
||||
|
||||
val dispatchAction = if (wasTwinsOnboardingShown) {
|
||||
val step = when {
|
||||
!scanResponse.twinsIsTwinned() -> TwinCardsStep.CreateFirstWallet
|
||||
twinCardsState.walletBalance.balanceIsToppedUp() -> TwinCardsStep.Done
|
||||
else -> TwinCardsStep.TopUpWallet
|
||||
}
|
||||
TwinCardsAction.SetStepOfScreen(step)
|
||||
} else {
|
||||
TwinCardsAction.SetStepOfScreen(TwinCardsStep.Welcome)
|
||||
}
|
||||
|
||||
store.dispatch(dispatchAction)
|
||||
}
|
||||
}
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.SetStepOfScreen -> {
|
||||
when (action.step) {
|
||||
is TwinCardsStep.WelcomeOnly, TwinCardsStep.Welcome -> {
|
||||
Analytics.send(Onboarding.Twins.ScreenOpened())
|
||||
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::saveTwinsOnboardingShownUseCase).invoke()
|
||||
}
|
||||
}
|
||||
is TwinCardsStep.CreateFirstWallet -> {
|
||||
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
|
||||
Analytics.send(Onboarding.Twins.SetupStarted())
|
||||
}
|
||||
TwinCardsStep.TopUpWallet -> {
|
||||
Analytics.send(Onboarding.Topup.ScreenOpened())
|
||||
store.dispatch(TwinCardsAction.Balance.Update)
|
||||
}
|
||||
TwinCardsStep.Done -> {
|
||||
finishCardActivation()
|
||||
postUi(500) { store.dispatch(TwinCardsAction.Confetti.Show) }
|
||||
}
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.Warning,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
TwinCardsStep.CreateThirdWallet,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Wallet.LaunchFirstStep -> {
|
||||
val manager = TwinCardsManager(card = getScanResponse().card)
|
||||
store.dispatch(TwinCardsAction.CardsManager.Set(manager))
|
||||
|
||||
scope.launch {
|
||||
when (val result = manager.createFirstWallet(action.initialMessage)) {
|
||||
is CompletionResult.Success -> {
|
||||
// remove wallet only after first step of retwin
|
||||
scope.launch {
|
||||
userWalletsListManager.delete(
|
||||
listOfNotNull(UserWalletIdBuilder.scanResponse(getScanResponse()).build()),
|
||||
)
|
||||
}
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
startCardActivation(result.data.cardId)
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateSecondWallet))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Wallet.LaunchSecondStep -> {
|
||||
val manager = twinCardsState.twinCardsManager ?: return
|
||||
|
||||
scope.launch {
|
||||
val result = manager.createSecondWallet(
|
||||
action.initialMessage,
|
||||
action.preparingMessage,
|
||||
action.creatingWalletMessage,
|
||||
)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
startCardActivation(result.data.cardId)
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
store.dispatch(TwinCardsAction.SetPairCardId(result.data.cardId))
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateThirdWallet))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Wallet.LaunchThirdStep -> {
|
||||
val manager = twinCardsState.twinCardsManager ?: return
|
||||
|
||||
scope.launch {
|
||||
when (val result = manager.complete(action.message)) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.Twins.SetupFinished())
|
||||
updateScanResponse(result.data)
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet))
|
||||
}
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> showCardVerificationFailedDialog(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Balance.Update -> {
|
||||
val walletManager = if (twinCardsState.walletManager != null) {
|
||||
twinCardsState.walletManager
|
||||
} else {
|
||||
val wmFactory = runBlocking {
|
||||
store.inject(DaggerGraphState::blockchainSDKFactory).getWalletManagerFactorySync()
|
||||
}
|
||||
val walletManager = wmFactory?.makePrimaryWalletManager(getScanResponse()).guard {
|
||||
val message = "Loading cancelled. Cause: wallet manager didn't created"
|
||||
val customError = TapError.CustomError(message)
|
||||
store.dispatchErrorNotification(customError)
|
||||
return
|
||||
}
|
||||
dispatch(TwinCardsAction.SetWalletManager(walletManager))
|
||||
walletManager
|
||||
}
|
||||
val isLoadedBefore = twinCardsState.walletBalance.state != ProgressState.Loading
|
||||
val balanceIsLoading = twinCardsState.walletBalance.copy(
|
||||
currency = Currency.Blockchain(
|
||||
walletManager.wallet.blockchain,
|
||||
walletManager.wallet.publicKey.derivationPath?.rawPath,
|
||||
),
|
||||
state = ProgressState.Loading,
|
||||
error = null,
|
||||
criticalError = null,
|
||||
)
|
||||
store.dispatch(TwinCardsAction.Balance.Set(balanceIsLoading))
|
||||
|
||||
scope.launch {
|
||||
val loadedBalance = onboardingManager?.updateBalance(walletManager) ?: return@launch
|
||||
loadedBalance.criticalError?.let(store::dispatchErrorNotification)
|
||||
delay(if (isLoadedBefore) 0 else 300)
|
||||
withMainContext {
|
||||
store.dispatch(TwinCardsAction.Balance.Set(loadedBalance))
|
||||
store.dispatch(TwinCardsAction.Balance.SetCriticalError(loadedBalance.criticalError))
|
||||
store.dispatch(TwinCardsAction.Balance.SetNonCriticalError(loadedBalance.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.Balance.Set -> {
|
||||
if (action.balance.balanceIsToppedUp()) {
|
||||
OnboardingHelper.sendToppedUpEvent(getScanResponse())
|
||||
store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done))
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.ShowAddressInfoDialog -> {
|
||||
val addressData = twinCardsState.walletManager?.getAddressData() ?: return
|
||||
|
||||
val appDialog = AppDialog.AddressInfoDialog(twinCardsState.walletBalance.currency, addressData)
|
||||
store.dispatchDialogShow(appDialog)
|
||||
}
|
||||
is TwinCardsAction.TopUp -> {
|
||||
val walletManager = twinCardsState.walletManager.guard {
|
||||
store.dispatchDebugErrorNotification("NPE: WalletManager")
|
||||
return
|
||||
}
|
||||
|
||||
OnboardingHelper.handleTopUpAction(
|
||||
walletManager = walletManager,
|
||||
scanResponse = onboardingManager?.scanResponse ?: return,
|
||||
)
|
||||
}
|
||||
TwinCardsAction.Done -> {
|
||||
val scanResponse = getScanResponse()
|
||||
when (twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
store.dispatchOnMain(GlobalAction.Onboarding.Stop)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
}
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
scope.launch {
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
if (walletsRepository.shouldSaveUserWalletsSync()) {
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
} else {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.SaveScannedTwinCardAndNavigateToWallet -> {
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(
|
||||
scanResponse = action.scanResponse,
|
||||
)
|
||||
}
|
||||
is TwinCardsAction.OnBackPressed -> {
|
||||
if (twinCardsState.twinningInProgress) {
|
||||
store.dispatchDialogShow(OnboardingDialog.TwinningProcessNotCompleted)
|
||||
} else {
|
||||
val onOkCallback = {
|
||||
val shouldReturnCardBack = twinCardsState.mode == CreateTwinWalletMode.CreateWallet &&
|
||||
twinCardsState.currentStep != TwinCardsStep.TopUpWallet &&
|
||||
twinCardsState.currentStep != TwinCardsStep.Done
|
||||
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(TwinCardsAction.CardsManager.Release)
|
||||
|
||||
action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
|
||||
store.dispatchNavigationAction { popTo(routeClass = getPopBackScreen()) }
|
||||
}
|
||||
}
|
||||
store.dispatchDialogShow(OnboardingDialog.InterruptOnboarding(onOkCallback))
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCardVerificationFailedDialog(error: TangemError) {
|
||||
if (error is TangemSdkError.CardVerificationFailed) {
|
||||
Analytics.send(
|
||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(
|
||||
AnalyticsParam.ScreensSources.Onboarding,
|
||||
),
|
||||
)
|
||||
|
||||
val resource = error.localizedDescriptionRes()
|
||||
val resId = resource.resId ?: R.string.common_unknown_error
|
||||
val resArgs = resource.args.map { it.value }
|
||||
|
||||
store.inject(DaggerGraphState::uiMessageSender).send(
|
||||
message = Dialogs.cardVerificationFailed(
|
||||
errorDescription = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
onRequestSupport = {
|
||||
mainScope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase).invoke(
|
||||
type = FeedbackEmailType.CardAttestationFailed,
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPopBackScreen(): KClass<out AppRoute> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
return if (userWalletsListManager.hasUserWallets) {
|
||||
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync!! }.getOrElse { false }
|
||||
|
||||
if (isLocked) {
|
||||
AppRoute.Welcome::class
|
||||
} else {
|
||||
AppRoute.Wallet::class
|
||||
}
|
||||
} else {
|
||||
AppRoute.Home::class
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.domain.common.getTwinCardNumber
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
object TwinCardsReducer {
|
||||
fun reduce(action: Action, state: AppState): TwinCardsState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun internalReduce(action: Action, state: AppState): TwinCardsState {
|
||||
if (action !is TwinCardsAction) return state.twinCardsState
|
||||
|
||||
var state = state.twinCardsState
|
||||
|
||||
when (action) {
|
||||
is TwinCardsAction.IfTwinsPrepareState -> {
|
||||
state = if (action.scanResponse.cardTypesResolver.isTangemTwins()) {
|
||||
val cardNumber = action.scanResponse.card.getTwinCardNumber() ?: return state
|
||||
|
||||
TwinCardsState(
|
||||
cardNumber = cardNumber,
|
||||
mode = CreateTwinWalletMode.CreateWallet,
|
||||
)
|
||||
} else {
|
||||
TwinCardsState()
|
||||
}
|
||||
}
|
||||
is TwinCardsAction.SetMode -> {
|
||||
state = state.copy(
|
||||
mode = action.mode,
|
||||
userWasUnderstandIfWalletRecreate = false,
|
||||
)
|
||||
}
|
||||
is TwinCardsAction.SetStepOfScreen -> {
|
||||
state = state.copy(
|
||||
currentStep = action.step,
|
||||
welcomeOnlyScanResponse = (action.step as? TwinCardsStep.WelcomeOnly)?.scanResponse,
|
||||
)
|
||||
}
|
||||
is TwinCardsAction.SetUserUnderstand -> {
|
||||
state = state.copy(userWasUnderstandIfWalletRecreate = action.isUnderstand)
|
||||
}
|
||||
is TwinCardsAction.CardsManager.Set -> {
|
||||
state = state.copy(twinCardsManager = action.manager)
|
||||
}
|
||||
TwinCardsAction.CardsManager.Release -> {
|
||||
state = state.copy(twinCardsManager = null)
|
||||
}
|
||||
is TwinCardsAction.SetPairCardId -> {
|
||||
state = state.copy(pairCardId = action.cardId)
|
||||
}
|
||||
is TwinCardsAction.SetWalletManager -> {
|
||||
state = state.copy(walletManager = action.walletManager)
|
||||
}
|
||||
is TwinCardsAction.Balance.Set -> {
|
||||
state = state.copy(walletBalance = action.balance)
|
||||
}
|
||||
is TwinCardsAction.Balance.SetCriticalError -> {
|
||||
state = state.copy(balanceCriticalError = action.error)
|
||||
}
|
||||
is TwinCardsAction.Balance.SetNonCriticalError -> {
|
||||
state = state.copy(balanceNonCriticalError = action.error)
|
||||
}
|
||||
is TwinCardsAction.Confetti.Show -> {
|
||||
state = state.copy(showConfetti = true)
|
||||
}
|
||||
is TwinCardsAction.Confetti.Hide -> {
|
||||
state = state.copy(showConfetti = false)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class TwinCardsState(
|
||||
// if cardNumber = null -> the scanned card is not Twin
|
||||
val cardNumber: TwinCardNumber? = null,
|
||||
val mode: CreateTwinWalletMode = CreateTwinWalletMode.CreateWallet,
|
||||
val currentStep: TwinCardsStep = TwinCardsStep.Welcome,
|
||||
val userWasUnderstandIfWalletRecreate: Boolean = false,
|
||||
// available only when creating or recreating wallets
|
||||
val twinCardsManager: TwinCardsManager? = null,
|
||||
|
||||
// for the onboarding
|
||||
val pairCardId: String? = null, // available after create\recreate wallets
|
||||
val walletManager: WalletManager? = null,
|
||||
val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(),
|
||||
val balanceNonCriticalError: TapError? = null,
|
||||
val balanceCriticalError: TapError? = null,
|
||||
val showConfetti: Boolean = false,
|
||||
val welcomeOnlyScanResponse: ScanResponse? = null,
|
||||
) : StateType {
|
||||
|
||||
val steps: List<TwinCardsStep>
|
||||
get() = when (mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> listOf(
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.CreateFirstWallet,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
TwinCardsStep.CreateThirdWallet,
|
||||
TwinCardsStep.TopUpWallet,
|
||||
TwinCardsStep.Done,
|
||||
)
|
||||
is CreateTwinWalletMode.RecreateWallet -> listOf(
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.CreateFirstWallet,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
TwinCardsStep.CreateThirdWallet,
|
||||
TwinCardsStep.Done,
|
||||
)
|
||||
}
|
||||
|
||||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
|
||||
val twinningInProgress: Boolean
|
||||
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
|
||||
}
|
||||
|
||||
sealed class CreateTwinWalletMode {
|
||||
data object CreateWallet : CreateTwinWalletMode()
|
||||
|
||||
data class RecreateWallet(
|
||||
val scanResponse: ScanResponse,
|
||||
) : CreateTwinWalletMode()
|
||||
}
|
||||
|
||||
sealed class TwinCardsStep {
|
||||
object None : TwinCardsStep()
|
||||
data class WelcomeOnly(
|
||||
val scanResponse: ScanResponse,
|
||||
) : TwinCardsStep()
|
||||
|
||||
object Welcome : TwinCardsStep()
|
||||
object Warning : TwinCardsStep()
|
||||
object CreateFirstWallet : TwinCardsStep()
|
||||
object CreateSecondWallet : TwinCardsStep()
|
||||
object CreateThirdWallet : TwinCardsStep()
|
||||
|
||||
// for the onboarding
|
||||
object TopUpWallet : TwinCardsStep()
|
||||
object Done : TwinCardsStep()
|
||||
}
|
||||
|
|
@ -1,460 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import com.tangem.Message
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.ShareElement
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.domain.twins.TwinsCardWidget
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.extensions.stripZeroPlainString
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@AndroidEntryPoint
|
||||
internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>() {
|
||||
|
||||
private val mainBinding by lazy { binding.vMain }
|
||||
private var previousStep: TwinCardsStep = TwinCardsStep.None
|
||||
|
||||
private lateinit var twinsWidget: TwinsCardWidget
|
||||
private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
store.dispatch(TwinCardsAction.Init)
|
||||
}
|
||||
|
||||
override fun configureTransitions() {
|
||||
when (store.state.twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
super.configureTransitions()
|
||||
}
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
configureDefaultTransactions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider { getActualScanResponse() },
|
||||
)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addBackPressHandler(this)
|
||||
|
||||
reconfigureLayoutForTwins(mainBinding.onboardingTopContainer)
|
||||
|
||||
val typedValue = TypedValue()
|
||||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
val deviceScaleFactorForWelcomeState = typedValue.float
|
||||
|
||||
twinsWidget = TwinsCardWidget(
|
||||
LeapfrogWidget(mainBinding.onboardingTopContainer.cardsContainer),
|
||||
deviceScaleFactorForWelcomeState,
|
||||
) {
|
||||
285f * deviceScaleFactorForWelcomeState
|
||||
}
|
||||
btnRefreshBalanceWidget =
|
||||
RefreshBalanceWidget(mainBinding.onboardingTopContainer.onboardingWalletContainer)
|
||||
|
||||
binding.toolbar.title = getText(R.string.twins_recreate_toolbar)
|
||||
|
||||
mainBinding.onboardingTopContainer.imvTwinFrontCard.load(Artwork.TWIN_CARD_1_URL) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
|
||||
mainBinding.onboardingTopContainer.imvTwinBackCard.load(Artwork.TWIN_CARD_2_URL) {
|
||||
placeholder(R.drawable.card_placeholder_white)
|
||||
error(R.drawable.card_placeholder_white)
|
||||
fallback(R.drawable.card_placeholder_white)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setStatusBarColor(R.color.background_primary)
|
||||
}
|
||||
|
||||
private fun reconfigureLayoutForTwins(containerBinding: LayoutOnboardingContainerTopBinding) =
|
||||
with(containerBinding) {
|
||||
imvFrontCard.hide()
|
||||
imvCardBackground.hide()
|
||||
cardsContainer.show()
|
||||
|
||||
imvTwinFrontCard.transitionName = ShareElement.imvFrontCard
|
||||
imvTwinBackCard.transitionName = ShareElement.imvBackCard
|
||||
|
||||
// if don't this, the bg_circle_... is overflow the app_bar
|
||||
binding.appBar.bringToFront()
|
||||
}
|
||||
|
||||
override fun subscribeToStore() {
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.twinCardsState == newState.twinCardsState
|
||||
}.select { it.twinCardsState }
|
||||
}
|
||||
storeSubscribersList.add(this)
|
||||
}
|
||||
|
||||
override fun newState(state: TwinCardsState) {
|
||||
if (activity == null || view == null) return
|
||||
|
||||
pbBinding.pbState.max = state.steps.size - 1
|
||||
pbBinding.pbState.progress = state.progress
|
||||
|
||||
when (state.currentStep) {
|
||||
is TwinCardsStep.WelcomeOnly -> setupWelcomeOnlyState(state, state.currentStep.scanResponse)
|
||||
is TwinCardsStep.Welcome -> setupWelcomeState(state)
|
||||
is TwinCardsStep.Warning -> setupWarningState(state)
|
||||
is TwinCardsStep.CreateFirstWallet -> setupCreateFirstWalletState(state)
|
||||
is TwinCardsStep.CreateSecondWallet -> setupCreateSecondWalletState(state)
|
||||
is TwinCardsStep.CreateThirdWallet -> setupCreateThirdWalletState(state)
|
||||
is TwinCardsStep.TopUpWallet -> setupTopUpWalletState(state)
|
||||
is TwinCardsStep.Done -> setupDoneState(state)
|
||||
else -> {}
|
||||
}
|
||||
|
||||
setBalance(state)
|
||||
showConfetti(state.showConfetti)
|
||||
previousStep = state.currentStep
|
||||
}
|
||||
|
||||
private fun setBalance(state: TwinCardsState) {
|
||||
if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return
|
||||
|
||||
with(mainBinding.onboardingTopContainer.onboardingTvBalance) {
|
||||
if (state.balanceCriticalError == null) {
|
||||
val balanceValue = state.walletBalance.value.stripZeroPlainString()
|
||||
tvBalanceValue.text = balanceValue
|
||||
tvBalanceCurrency.text = state.walletBalance.currency.currencySymbol
|
||||
} else {
|
||||
tvBalanceValue.text = "–"
|
||||
tvBalanceCurrency.text = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWelcomeOnlyState(state: TwinCardsState, scanResponse: ScanResponse) {
|
||||
setupWelcomeState(state) {
|
||||
store.dispatch(TwinCardsAction.SaveScannedTwinCardAndNavigateToWallet(scanResponse))
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.None))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWelcomeState(state: TwinCardsState) {
|
||||
setupWelcomeState(state) {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupWelcomeState(state: TwinCardsState, onMainButtonClick: VoidCallback) =
|
||||
with(mainBinding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false)
|
||||
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.show()
|
||||
pbBinding.pbState.hide()
|
||||
|
||||
tvHeader.setText(R.string.twins_onboarding_subtitle)
|
||||
tvBody.text = getString(
|
||||
R.string.twins_onboarding_description_format,
|
||||
state.cardNumber?.pairIndexNumber(),
|
||||
)
|
||||
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener { onMainButtonClick() }
|
||||
}
|
||||
|
||||
private fun setupWarningState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
twinsWidget.toWelcome(false)
|
||||
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
|
||||
pbBinding.pbState.hide()
|
||||
chbUnderstand.show()
|
||||
|
||||
tvHeader.setText(R.string.common_warning)
|
||||
tvBody.setText(R.string.twins_recreate_warning)
|
||||
|
||||
chbUnderstand.setOnCheckedChangeListener { _, isChecked ->
|
||||
store.dispatch(TwinCardsAction.SetUserUnderstand(isChecked))
|
||||
}
|
||||
btnMainAction.isEnabled = state.userWasUnderstandIfWalletRecreate
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.CreateFirstWallet))
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateFirstWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.root.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleLarge.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMedium.hide()
|
||||
mainBinding.onboardingTopContainer.onboardingTwinsWelcomeBg.bgCircleMin.hide()
|
||||
chbUnderstand.hide()
|
||||
pbBinding.pbState.show()
|
||||
|
||||
mainBinding.onboardingTopContainer.onboardingWalletContainer.beginDelayedTransition()
|
||||
|
||||
when (previousStep) {
|
||||
TwinCardsStep.None -> {
|
||||
twinsWidget.toLeapfrog(false) {
|
||||
when (state.cardNumber) {
|
||||
TwinCardNumber.First -> {}
|
||||
TwinCardNumber.Second -> {
|
||||
switchToCard(
|
||||
state.cardNumber,
|
||||
false,
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
TwinCardsStep.Welcome, TwinCardsStep.Warning -> {
|
||||
twinsWidget.toLeapfrog(onEnd = { switchToCard(state.cardNumber) })
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
val twinIndexNumber = state.cardNumber?.indexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchFirstStep(
|
||||
initialMessage = Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.isClickable = false
|
||||
}
|
||||
|
||||
private fun setupCreateSecondWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
switchToCard(state.cardNumber?.pairNumber())
|
||||
|
||||
val twinPairIndexNumber = state.cardNumber?.pairIndexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinPairIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text =
|
||||
getString(R.string.twins_recreate_button_format, twinPairIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchSecondStep(
|
||||
Message(
|
||||
getString(
|
||||
R.string.twins_recreate_title_format,
|
||||
twinPairIndexNumber,
|
||||
),
|
||||
),
|
||||
Message(getString(R.string.twins_recreate_title_preparing)),
|
||||
Message(getString(R.string.twins_recreate_title_creating_wallet)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateThirdWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
switchToCard(state.cardNumber)
|
||||
|
||||
val twinIndexNumber = state.cardNumber?.indexNumber()
|
||||
tvHeader.text = getString(R.string.twins_recreate_title_format, twinIndexNumber)
|
||||
tvBody.setText(R.string.onboarding_twins_interrupt_warning)
|
||||
|
||||
btnMainAction.text = getString(R.string.twins_recreate_button_format, twinIndexNumber)
|
||||
btnMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(
|
||||
TwinCardsAction.Wallet.LaunchThirdStep(
|
||||
Message(getString(R.string.twins_recreate_title_format, twinIndexNumber)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTopUpWalletState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
when (previousStep) {
|
||||
TwinCardsStep.None, TwinCardsStep.Welcome -> {
|
||||
when (state.cardNumber) {
|
||||
TwinCardNumber.First -> {
|
||||
twinsWidget.leapfrogWidget.unfold(true) {
|
||||
twinsWidget.toActivate(true)
|
||||
}
|
||||
}
|
||||
TwinCardNumber.Second -> {
|
||||
switchToCard(state.cardNumber, true) {
|
||||
twinsWidget.toActivate(true)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
TwinCardsStep.CreateThirdWallet -> {
|
||||
twinsWidget.toActivate()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
if (availableForBuy(getActualScanResponse(), state.walletBalance)) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.TopUp)
|
||||
}
|
||||
btnAlternativeAction.isVisible = true
|
||||
btnAlternativeAction.setText(R.string.onboarding_top_up_button_show_wallet_address)
|
||||
btnAlternativeAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
} else {
|
||||
btnMainAction.setText(R.string.onboarding_button_receive_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.ShowAddressInfoDialog)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
}
|
||||
|
||||
tvHeader.setText(R.string.onboarding_topup_title)
|
||||
tvBody.setText(R.string.onboarding_top_up_body)
|
||||
btnMainAction.icon = null
|
||||
|
||||
btnRefreshBalanceWidget.changeState(state.walletBalance.state)
|
||||
if (btnRefreshBalanceWidget.isShowing != true) {
|
||||
btnRefreshBalanceWidget.mainView.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Balance.Update)
|
||||
}
|
||||
}
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.setBackgroundDrawable(
|
||||
requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8),
|
||||
)
|
||||
mainBinding.onboardingTopContainer.imvCardBackground.backgroundTintList =
|
||||
requireContext().resources.getColorStateList(R.color.onboarding_card_background, null)
|
||||
|
||||
updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet_twins)
|
||||
}
|
||||
|
||||
private fun setupDoneState(state: TwinCardsState) = with(mainBinding.onboardingActionContainer) {
|
||||
btnMainAction.setText(R.string.common_continue)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
store.dispatch(TwinCardsAction.Done)
|
||||
}
|
||||
|
||||
btnAlternativeAction.isVisible = false
|
||||
btnAlternativeAction.isClickable = false
|
||||
|
||||
tvHeader.setText(R.string.onboarding_done_header)
|
||||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
val layout = when (state.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
|
||||
is CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
|
||||
}
|
||||
updateConstraints(state.currentStep, layout)
|
||||
}
|
||||
|
||||
private fun updateConstraints(currentStep: TwinCardsStep, @LayoutRes layoutId: Int) {
|
||||
if (this.previousStep == currentStep) return
|
||||
|
||||
with(mainBinding.onboardingTopContainer) {
|
||||
val constraintSet = ConstraintSet()
|
||||
constraintSet.clone(requireContext(), layoutId)
|
||||
constraintSet.applyTo(onboardingWalletContainer)
|
||||
val transition = InternalNoteLayoutTransition()
|
||||
transition.interpolator = OvershootInterpolator()
|
||||
TransitionManager.beginDelayedTransition(onboardingWalletContainer, transition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToCard(cardNumber: TwinCardNumber?, animate: Boolean = true, onEnd: VoidCallback = {}) {
|
||||
val position = cardNumber?.number ?: return
|
||||
|
||||
if (twinsWidget.leapfrogWidget.getViewPositionByIndex(1) != position - 1) {
|
||||
twinsWidget.leapfrogWidget.leap(animate, onEnd)
|
||||
} else {
|
||||
onEnd()
|
||||
}
|
||||
}
|
||||
|
||||
private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean {
|
||||
scanResponse ?: return false
|
||||
return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency)
|
||||
}
|
||||
|
||||
private fun getActualScanResponse(): ScanResponse {
|
||||
return store.state.twinCardsState.welcomeOnlyScanResponse
|
||||
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: store.state.detailsState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(
|
||||
TwinCardsAction.OnBackPressed { should, popAction ->
|
||||
store.dispatch(TwinCardsAction.Confetti.Hide)
|
||||
showConfetti(false)
|
||||
if (should) {
|
||||
switchToCard(TwinCardNumber.First, true, popAction)
|
||||
} else {
|
||||
popAction()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun TwinCardNumber?.indexNumber(): String {
|
||||
return this?.number?.toString() ?: ""
|
||||
}
|
||||
|
||||
private fun TwinCardNumber?.pairIndexNumber(): String {
|
||||
return this?.pairNumber()?.number?.toString() ?: ""
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.ui.dialog
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object TwinningProcessNotCompletedDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog)
|
||||
.setMessage(R.string.onboarding_twin_exit_warning)
|
||||
.setPositiveButton(R.string.warning_button_ok) { _, _ -> }
|
||||
.setOnDismissListener { store.dispatchDialogHide() }
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
|
||||
sealed class BackupDialog : StateDialog {
|
||||
data class UnfinishedBackupFound(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : BackupDialog()
|
||||
|
||||
data class ConfirmDiscardingBackup(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : BackupDialog()
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.Onboarding.*
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
class BackupMiddleware {
|
||||
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is BackupAction) handleBackupAction(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
|
||||
when (action) {
|
||||
is BackupAction.DiscardBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.DiscardSavedBackup -> {
|
||||
mainScope.launch {
|
||||
backupService.discardSavedBackup()
|
||||
|
||||
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
|
||||
val cardRepository = store.inject(DaggerGraphState::cardRepository)
|
||||
val unfinishedBackup = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
|
||||
|
||||
cardRepository.finishCardActivation(unfinishedBackup.card.cardId)
|
||||
onboardingRepository.clearUnfinishedFinalizeOnboarding()
|
||||
Analytics.send(Finished())
|
||||
}
|
||||
}
|
||||
is BackupAction.ResumeFoundUnfinishedBackup -> {
|
||||
if (action.unfinishedBackupScanResponse != null) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = action.unfinishedBackupScanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.ContinueFinalize,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +1,19 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.rekotlin.Action
|
||||
|
||||
sealed class OnboardingWalletAction : Action {
|
||||
data object Init : OnboardingWalletAction()
|
||||
data object GetToCreateWalletStep : OnboardingWalletAction()
|
||||
data object CreateWallet : OnboardingWalletAction()
|
||||
data class WalletWasCreated(
|
||||
val shouldSendAnalyticsEvent: Boolean,
|
||||
val result: CompletionResult<CreateProductWalletTaskResponse>,
|
||||
) : OnboardingWalletAction()
|
||||
|
||||
data object Done : OnboardingWalletAction()
|
||||
data class FinishOnboarding(val scope: CoroutineScope) : OnboardingWalletAction()
|
||||
|
||||
data object ResumeBackup : OnboardingWalletAction()
|
||||
|
||||
data class LoadArtwork(val cardArtworkUriForUnfinishedBackup: Uri? = null) : OnboardingWalletAction()
|
||||
class SetPrimaryCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||
class SetSecondCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||
class SetThirdCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction()
|
||||
|
||||
data object OnBackPressed : OnboardingWalletAction()
|
||||
|
||||
data class WalletSaved(val userWalletId: UserWalletId) : OnboardingWalletAction()
|
||||
}
|
||||
|
||||
sealed class OnboardingWallet2Action : OnboardingWalletAction() {
|
||||
data class Init(val maxProgress: Int) : OnboardingWallet2Action()
|
||||
data class SetDependencies(val maxProgress: Int) : OnboardingWallet2Action()
|
||||
|
||||
data class CreateWallet(
|
||||
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) : OnboardingWallet2Action()
|
||||
|
||||
data class ImportWallet(
|
||||
val mnemonicComponents: List<String>,
|
||||
val passphrase: String?,
|
||||
val seedPhraseSource: SeedPhraseSource,
|
||||
val callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) : OnboardingWallet2Action()
|
||||
|
||||
data class WalletWasCreated(
|
||||
val result: CompletionResult<CreateWalletResponse>,
|
||||
) : OnboardingWallet2Action()
|
||||
}
|
||||
|
||||
sealed class BackupAction : Action {
|
||||
|
||||
data object IntroduceBackup : BackupAction()
|
||||
data object StartBackup : BackupAction()
|
||||
data object SkipBackup : BackupAction()
|
||||
|
||||
data object ErrorInBackupCard : BackupAction()
|
||||
data object StartAddingPrimaryCard : BackupAction()
|
||||
data object ScanPrimaryCard : BackupAction()
|
||||
data class SetHasRing(val hasRing: Boolean) : BackupAction()
|
||||
|
||||
/**
|
||||
* Check for unfinished backup of standard Wallets
|
||||
* See more GlobalAction.Onboarding.StartForUnfinishedBackup
|
||||
*/
|
||||
data object CheckForUnfinishedBackup : BackupAction()
|
||||
|
||||
data object StartAddingBackupCards : BackupAction()
|
||||
data object AddBackupCard : BackupAction() {
|
||||
data class Success(val card: Card) : BackupAction()
|
||||
data class ChangeButtonLoading(val isLoading: Boolean) : BackupAction()
|
||||
}
|
||||
|
||||
data object FinishAddingBackupCards : BackupAction()
|
||||
|
||||
data object ShowAccessCodeInfoScreen : BackupAction()
|
||||
data object ShowEnterAccessCodeScreen : BackupAction()
|
||||
data class CheckAccessCode(val accessCode: String) : BackupAction()
|
||||
data class SetAccessCodeError(val error: AccessCodeError?) : BackupAction()
|
||||
data class SaveFirstAccessCode(val accessCode: String) : BackupAction()
|
||||
data class SaveAccessCodeConfirmation(val accessCodeConfirmation: String) : BackupAction()
|
||||
data object OnAccessCodeDialogClosed : BackupAction()
|
||||
|
||||
data object PrepareToWritePrimaryCard : BackupAction()
|
||||
data object WritePrimaryCard : BackupAction()
|
||||
data class PrepareToWriteBackupCard(val cardNumber: Int) : BackupAction()
|
||||
data class WriteBackupCard(val cardNumber: Int) : BackupAction()
|
||||
|
||||
data class FinishBackup(val withAnalytics: Boolean = true) : BackupAction()
|
||||
|
||||
data class BackupFinished(val userWalletId: UserWalletId?) : BackupAction()
|
||||
|
||||
data object DiscardBackup : BackupAction()
|
||||
data object DiscardSavedBackup : BackupAction()
|
||||
|
||||
data class ResumeFoundUnfinishedBackup(
|
||||
val unfinishedBackupScanResponse: ScanResponse?,
|
||||
) : BackupAction()
|
||||
|
||||
data class ResetBackupCard(val cardId: String) : BackupAction()
|
||||
}
|
||||
|
|
@ -1,849 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.net.Uri
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.ScreensSources
|
||||
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
|
||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.toWrappedList
|
||||
import com.tangem.core.ui.message.dialog.Dialogs
|
||||
import com.tangem.domain.common.extensions.withIOContext
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS
|
||||
import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
|
||||
import com.tangem.operations.attestation.OnlineCardVerifier
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.CreateProductWalletTaskResponse
|
||||
import com.tangem.sdk.extensions.localizedDescriptionRes
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
object OnboardingWalletMiddleware {
|
||||
val handler = onboardingWalletMiddleware
|
||||
}
|
||||
|
||||
private const val HIDE_PROGRESS_DELAY = 400L
|
||||
|
||||
private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
when (action) {
|
||||
is OnboardingWallet2Action -> handleWallet2Action(action)
|
||||
else -> handleWalletAction(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleWalletAction(action: Action) {
|
||||
if (action !is OnboardingWalletAction) return
|
||||
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
val card = scanResponse?.card
|
||||
|
||||
val onboardingWalletState = store.state.onboardingWalletState
|
||||
|
||||
when (action) {
|
||||
OnboardingWalletAction.Init -> {
|
||||
ifNotNull(onboardingManager, card) { manager, notNullCard ->
|
||||
mainScope.launch {
|
||||
if (!manager.isActivationStarted(notNullCard.cardId)) {
|
||||
Analytics.send(Onboarding.Started())
|
||||
}
|
||||
}
|
||||
}
|
||||
when {
|
||||
card == null -> {
|
||||
// it's possible when found unfinished backup
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
card.wallets.isNotEmpty() && card.backupStatus == CardDTO.BackupStatus.NoBackup -> {
|
||||
store.dispatch(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
card.wallets.isNotEmpty() && card.backupStatus?.isActive == true -> {
|
||||
store.dispatch(BackupAction.FinishBackup())
|
||||
}
|
||||
else -> {
|
||||
store.dispatch(OnboardingWalletAction.GetToCreateWalletStep)
|
||||
Analytics.send(Onboarding.CreateWallet.ScreenOpened())
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.LoadArtwork -> {
|
||||
scope.launch {
|
||||
val cardArtwork = when (onboardingManager) {
|
||||
null -> {
|
||||
// onboardingManager is null when backup started from previously interrupted
|
||||
val primaryCardId = backupService.primaryCardId
|
||||
val cardPublicKey = backupService.primaryPublicKey
|
||||
if (primaryCardId != null && cardPublicKey != null) {
|
||||
// uses when no scanResponse and backup state restored
|
||||
loadArtworkForCard(
|
||||
cardId = primaryCardId,
|
||||
cardPublicKey = cardPublicKey,
|
||||
defaultArtwork = action.cardArtworkUriForUnfinishedBackup,
|
||||
)
|
||||
} else {
|
||||
action.cardArtworkUriForUnfinishedBackup
|
||||
}
|
||||
}
|
||||
else -> onboardingManager.loadArtworkUrl()
|
||||
.takeIf { it != Artwork.DEFAULT_IMG_URL }
|
||||
?.let { Uri.parse(it) }
|
||||
}
|
||||
store.dispatchOnMain(OnboardingWalletAction.SetPrimaryCardArtworkUrl(cardArtwork))
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val result = tangemSdkManager.createProductWallet(
|
||||
scanResponse,
|
||||
globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(true, result))
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.WalletWasCreated -> {
|
||||
scanResponse ?: return
|
||||
when (val result = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
if (action.shouldSendAnalyticsEvent) {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
}
|
||||
// here we must use updated scanResponse after createWallet & derivation
|
||||
val updatedResponse = globalState.onboardingState.onboardingManager.scanResponse.copy(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
onboardingManager.scanResponse = updatedResponse
|
||||
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
mainScope.launch {
|
||||
onboardingManager.startActivation(updatedResponse.card.cardId)
|
||||
store.dispatchWithMain(OnboardingWalletAction.ResumeBackup)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is OnboardingWalletAction.FinishOnboarding -> {
|
||||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
navigateToWalletScreen()
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
}
|
||||
is OnboardingWalletAction.ResumeBackup -> {
|
||||
val newAction = when (val backupState = backupService.currentState) {
|
||||
BackupService.State.FinalizingPrimaryCard -> BackupAction.PrepareToWritePrimaryCard
|
||||
is BackupService.State.FinalizingBackupCard -> BackupAction.PrepareToWriteBackupCard(backupState.index)
|
||||
else -> {
|
||||
if (onboardingWalletState.backupState.backupStep == null ||
|
||||
onboardingWalletState.backupState.backupStep == BackupStep.InitBackup ||
|
||||
onboardingWalletState.backupState.backupStep == BackupStep.Finished
|
||||
) {
|
||||
Analytics.send(Onboarding.Backup.ScreenOpened())
|
||||
|
||||
val isWallet2 = scanResponse?.cardTypesResolver?.isWallet2() ?: false
|
||||
if (isWallet2) BackupAction.StartBackup else BackupAction.IntroduceBackup
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
newAction?.let { store.dispatch(it) }
|
||||
}
|
||||
OnboardingWalletAction.OnBackPressed -> handleOnBackPressed(onboardingWalletState)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleFinishBackup(scanResponse: ScanResponse, userWallet: UserWallet? = null) {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
|
||||
|
||||
val userWalletId = userWallet?.walletId ?: UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
if (backupState.hasRing && userWalletId != null) {
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::walletsRepository).setHasWalletsWithRing(userWalletId = userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
OnboardingHelper.saveWallet(
|
||||
alreadyCreatedWallet = userWallet,
|
||||
scanResponse = updatedScanResponse,
|
||||
accessCode = backupState.accessCode,
|
||||
backupCardsIds = backupState.backupCardIds,
|
||||
hasBackupError = backupState.hasBackupError,
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToWalletScreen() {
|
||||
mainScope.launch {
|
||||
val settingsRepository = store.inject(DaggerGraphState::settingsRepository)
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
if (tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen()) {
|
||||
delay(timeMillis = 1_800)
|
||||
store.dispatchNavigationAction { push(AppRoute.SaveWallet) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun readCard(): CompletionResult<ScanResponse> {
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
return store.inject(DaggerGraphState::scanCardProcessor).scan(analyticsSource = ScreensSources.Intro)
|
||||
}
|
||||
|
||||
private suspend fun loadArtworkForCard(cardId: String, cardPublicKey: ByteArray, defaultArtwork: Uri?): Uri {
|
||||
val onlineCardVerifier = store.inject(DaggerGraphState::onlineCardVerifier)
|
||||
|
||||
return when (val cardInfo = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
|
||||
is Result.Success -> {
|
||||
val artworkId = cardInfo.data.artwork?.id
|
||||
if (artworkId.isNullOrEmpty()) {
|
||||
defaultArtwork ?: Uri.EMPTY
|
||||
} else {
|
||||
Uri.parse(OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId))
|
||||
}
|
||||
}
|
||||
is Result.Failure -> defaultArtwork ?: Uri.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod")
|
||||
private fun handleWallet2Action(action: OnboardingWallet2Action) {
|
||||
val globalState = store.state.globalState
|
||||
val onboardingManager = globalState.onboardingState.onboardingManager
|
||||
|
||||
val scanResponse = onboardingManager?.scanResponse
|
||||
|
||||
when (action) {
|
||||
is OnboardingWallet2Action.Init -> {
|
||||
if (scanResponse?.cardTypesResolver?.isWallet2() == true) {
|
||||
store.dispatch(OnboardingWallet2Action.SetDependencies(action.maxProgress))
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.CreateWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (
|
||||
val result = tangemSdkManager.createProductWallet(
|
||||
scanResponse,
|
||||
globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Onboarding.CreateWallet.WalletCreatedSuccessfully())
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.ImportWallet -> {
|
||||
scanResponse ?: return
|
||||
scope.launch {
|
||||
val mediateResult = when (
|
||||
val result = tangemSdkManager.importWallet(
|
||||
scanResponse = scanResponse,
|
||||
passphrase = action.passphrase,
|
||||
mnemonic = action.mnemonicComponents.joinToString(" "),
|
||||
shouldReset = globalState.onboardingState.shouldResetOnCreate,
|
||||
)
|
||||
) {
|
||||
is CompletionResult.Success -> {
|
||||
val creationType = when (action.seedPhraseSource) {
|
||||
SeedPhraseSource.IMPORTED -> AnalyticsParam.WalletCreationType.SeedImport
|
||||
SeedPhraseSource.GENERATED -> AnalyticsParam.WalletCreationType.NewSeed
|
||||
}
|
||||
Analytics.send(
|
||||
event = Onboarding.CreateWallet.WalletCreatedSuccessfully(
|
||||
creationType = creationType,
|
||||
seedPhraseLength = action.mnemonicComponents.size,
|
||||
),
|
||||
)
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(false))
|
||||
val response = CreateWalletResponse(
|
||||
card = result.data.card,
|
||||
derivedKeys = result.data.derivedKeys,
|
||||
primaryCard = result.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
if (result.error is TangemSdkError.WalletAlreadyCreated) {
|
||||
handleActivationError()
|
||||
}
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
withMainContext {
|
||||
action.callback(mediateResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingWallet2Action.WalletWasCreated -> {
|
||||
val result = when (val mediateResult = action.result) {
|
||||
is CompletionResult.Success -> {
|
||||
val response = CreateProductWalletTaskResponse(
|
||||
card = mediateResult.data.card,
|
||||
derivedKeys = mediateResult.data.derivedKeys,
|
||||
primaryCard = mediateResult.data.primaryCard,
|
||||
)
|
||||
CompletionResult.Success(response)
|
||||
}
|
||||
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(mediateResult.error)
|
||||
}
|
||||
}
|
||||
// do not send analytics event for wallet2 flow, cause its already sent
|
||||
store.dispatchOnMain(OnboardingWalletAction.WalletWasCreated(false, result))
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleActivationError() {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.WalletActivationError(
|
||||
onConfirm = {
|
||||
store.dispatch(GlobalAction.Onboarding.ShouldResetCardOnCreate(true))
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateScanResponseAfterBackup(scanResponse: ScanResponse, backupState: BackupState): ScanResponse {
|
||||
val card = if (backupState.backupCardsNumber > 0) {
|
||||
val cardsCount = backupState.backupCardsNumber
|
||||
scanResponse.card.copy(
|
||||
backupStatus = CardDTO.BackupStatus.Active(cardCount = cardsCount),
|
||||
isAccessCodeSet = true,
|
||||
)
|
||||
} else {
|
||||
scanResponse.card
|
||||
}
|
||||
return scanResponse.copy(card = card)
|
||||
}
|
||||
|
||||
class BackupMiddleware {
|
||||
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
if (action is BackupAction) handleBackupAction(state, action)
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "ComplexMethod", "MagicNumber")
|
||||
private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) {
|
||||
if (DemoHelper.tryHandle(appState, action)) return
|
||||
val globalState = appState()?.globalState ?: return
|
||||
val onboardingWalletState = appState()?.onboardingWalletState ?: return
|
||||
|
||||
val backupState = onboardingWalletState.backupState
|
||||
var scanResponse = globalState.onboardingState.onboardingManager?.scanResponse
|
||||
val card = scanResponse?.card
|
||||
|
||||
when (action) {
|
||||
is BackupAction.StartBackup -> {
|
||||
Analytics.send(Onboarding.Backup.Started())
|
||||
backupService.discardSavedBackup()
|
||||
val primaryCard = scanResponse?.primaryCard
|
||||
if (primaryCard != null) {
|
||||
backupService.setPrimaryCard(primaryCard)
|
||||
store.dispatch(BackupAction.StartAddingBackupCards)
|
||||
} else {
|
||||
store.dispatch(BackupAction.StartAddingPrimaryCard)
|
||||
}
|
||||
}
|
||||
is BackupAction.ScanPrimaryCard -> {
|
||||
val iconScanRes = if (scanResponse?.cardTypesResolver?.isRing() == true) {
|
||||
R.drawable.img_hand_scan_ring
|
||||
} else {
|
||||
null
|
||||
}
|
||||
backupService.readPrimaryCard(iconScanRes = iconScanRes, cardId = card?.cardId) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.StartAddingBackupCards)
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.AddBackupCard -> {
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.ChangeButtonLoading(true))
|
||||
backupService.addBackupCard { result ->
|
||||
backupService.skipCompatibilityChecks = false
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).sdk.config.filter.cardIdFilter = null
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.ChangeButtonLoading(false))
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
updateArtworks(backupService.addedBackupCardsCount, result.data)
|
||||
store.dispatchOnMain(BackupAction.AddBackupCard.Success(result.data))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
when (val error = result.error) {
|
||||
is TangemSdkError.CardVerificationFailed -> {
|
||||
Analytics.send(
|
||||
event = OnboardingAnalyticsEvent.Onboarding.OfflineAttestationFailed(
|
||||
ScreensSources.Backup,
|
||||
),
|
||||
)
|
||||
|
||||
val resource = error.localizedDescriptionRes()
|
||||
val resId = resource.resId ?: com.tangem.core.ui.R.string.common_unknown_error
|
||||
val resArgs = resource.args.map { it.value }
|
||||
|
||||
store.inject(DaggerGraphState::uiMessageSender).send(
|
||||
message = Dialogs.cardVerificationFailed(
|
||||
errorDescription = resourceReference(id = resId, resArgs.toWrappedList()),
|
||||
onRequestSupport = {
|
||||
mainScope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||
.invoke(type = FeedbackEmailType.CardAttestationFailed)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
BackupDialog.ResetBackupCard(error.cardId),
|
||||
),
|
||||
)
|
||||
}
|
||||
is TangemSdkError.IssuerSignatureLoadingFailed -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(BackupDialog.AttestationFailed),
|
||||
)
|
||||
}
|
||||
else -> Analytics.sendException(ExceptionAnalyticsEvent(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.FinishAddingBackupCards -> {
|
||||
if (backupService.addedBackupCardsCount == backupState.maxBackupCards) {
|
||||
store.dispatchOnMain(BackupAction.ShowAccessCodeInfoScreen)
|
||||
} else {
|
||||
store.dispatchOnMain(GlobalAction.ShowDialog(BackupDialog.AddMoreBackupCards))
|
||||
}
|
||||
}
|
||||
is BackupAction.ShowEnterAccessCodeScreen -> {
|
||||
Analytics.send(Onboarding.Backup.SettingAccessCodeStarted())
|
||||
}
|
||||
is BackupAction.CheckAccessCode -> {
|
||||
Analytics.send(Onboarding.Backup.AccessCodeEntered())
|
||||
if (action.accessCode.length < 4) {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(AccessCodeError.CodeTooShort))
|
||||
} else {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(null))
|
||||
store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode))
|
||||
}
|
||||
}
|
||||
is BackupAction.SaveAccessCodeConfirmation -> {
|
||||
Analytics.send(Onboarding.Backup.AccessCodeReEntered())
|
||||
if (action.accessCodeConfirmation == backupState.accessCode) {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(null))
|
||||
backupService.setAccessCode(action.accessCodeConfirmation)
|
||||
store.dispatch(BackupAction.PrepareToWritePrimaryCard)
|
||||
} else {
|
||||
store.dispatch(BackupAction.SetAccessCodeError(AccessCodeError.CodesDoNotMatch))
|
||||
}
|
||||
}
|
||||
is BackupAction.WritePrimaryCard -> {
|
||||
val isRing = scanResponse?.cardTypesResolver?.isRing() == true
|
||||
val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null
|
||||
|
||||
store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing))
|
||||
|
||||
tangemSdkManager.changeProductType(isRing)
|
||||
backupService.proceedBackup(iconScanRes = iconScanRes) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(1))
|
||||
}
|
||||
is CompletionResult.Failure -> Unit
|
||||
}
|
||||
tangemSdkManager.clearProductType()
|
||||
}
|
||||
}
|
||||
is BackupAction.WriteBackupCard -> {
|
||||
val cardIndex = if (action.cardNumber > 0) action.cardNumber - 1 else action.cardNumber
|
||||
val backupCard = backupState.backupCards.getOrNull(cardIndex)
|
||||
val isRing = backupCard.isRing()
|
||||
val iconScanRes = if (isRing) R.drawable.img_hand_scan_ring else null
|
||||
|
||||
store.dispatchOnMain(BackupAction.SetHasRing(hasRing = isRing))
|
||||
|
||||
tangemSdkManager.changeProductType(isRing)
|
||||
backupService.proceedBackup(iconScanRes = iconScanRes) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val backupValidator = BackupValidator()
|
||||
if (!backupValidator.isValidBackupStatus(CardDTO(result.data))) {
|
||||
store.dispatchOnMain(BackupAction.ErrorInBackupCard)
|
||||
}
|
||||
if (backupService.currentState == BackupService.State.Finished) {
|
||||
store.dispatchOnMain(BackupAction.FinishBackup())
|
||||
} else {
|
||||
store.dispatchOnMain(BackupAction.PrepareToWriteBackupCard(action.cardNumber + 1))
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
when (val error = result.error) {
|
||||
is TangemSdkError.BackupFailedNotEmptyWallets -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
BackupDialog.ResetBackupCard(error.cardId),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tangemSdkManager.clearProductType()
|
||||
}
|
||||
}
|
||||
is BackupAction.DiscardBackup -> {
|
||||
backupService.discardSavedBackup()
|
||||
}
|
||||
is BackupAction.DiscardSavedBackup -> {
|
||||
mainScope.launch {
|
||||
backupService.primaryCardId?.let { // always null for onboarding v2
|
||||
Analytics.send(Onboarding.Finished())
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(it)
|
||||
}
|
||||
backupService.discardSavedBackup()
|
||||
|
||||
val isOnboardingV2Enabled = store.inject(DaggerGraphState::onboardingV2FeatureToggles)
|
||||
.isOnboardingV2Enabled
|
||||
|
||||
if (isOnboardingV2Enabled) {
|
||||
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
|
||||
val cardRepository = store.inject(DaggerGraphState::cardRepository)
|
||||
val unfinishedBackup = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
|
||||
|
||||
cardRepository.finishCardActivation(unfinishedBackup.card.cardId)
|
||||
onboardingRepository.clearUnfinishedFinalizeOnboarding()
|
||||
Analytics.send(Onboarding.Finished())
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.CheckForUnfinishedBackup -> {
|
||||
val isOnboardingV2Enabled = store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled
|
||||
|
||||
if (isOnboardingV2Enabled) {
|
||||
val onboardingRepository = store.inject(DaggerGraphState::onboardingRepository)
|
||||
|
||||
mainScope.launch {
|
||||
val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound(onboardingScanResponse)))
|
||||
}
|
||||
} else if (backupService.hasIncompletedBackup) {
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.UnfinishedBackupFound()))
|
||||
}
|
||||
}
|
||||
is BackupAction.ResumeFoundUnfinishedBackup -> {
|
||||
if (action.unfinishedBackupScanResponse != null) {
|
||||
// onboarding V2
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = action.unfinishedBackupScanResponse,
|
||||
mode = AppRoute.Onboarding.Mode.ContinueFinalize,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
store.dispatch(
|
||||
GlobalAction.Onboarding.StartForUnfinishedBackup(
|
||||
addedBackupCardsCount = backupService.addedBackupCardsCount,
|
||||
),
|
||||
)
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
|
||||
}
|
||||
}
|
||||
is BackupAction.SkipBackup -> {
|
||||
Analytics.send(Onboarding.Backup.Skipped())
|
||||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
scope.launch {
|
||||
launch {
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(
|
||||
cardIds = gatherCardIds(backupState, card),
|
||||
)
|
||||
}
|
||||
|
||||
with(scanResponse) {
|
||||
if (this == null) {
|
||||
when (val result = readCard()) {
|
||||
is CompletionResult.Success -> handleFinishBackup(result.data)
|
||||
is CompletionResult.Failure -> store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
} else {
|
||||
handleFinishBackup(scanResponse = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is BackupAction.FinishBackup -> {
|
||||
scope.launch {
|
||||
if (action.withAnalytics) {
|
||||
Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber))
|
||||
}
|
||||
|
||||
var userWallet: UserWallet? = null
|
||||
if (scanResponse != null) {
|
||||
scanResponse = updateScanResponseAfterBackup(scanResponse!!, backupState)
|
||||
userWallet = createUserWallet(
|
||||
scanResponse = requireNotNull(value = scanResponse, lazyMessage = { "ScanResponse is null" }),
|
||||
backupState = backupState,
|
||||
)
|
||||
} else {
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
|
||||
when (val result = readCard()) {
|
||||
is CompletionResult.Failure -> {
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
return@launch
|
||||
}
|
||||
is CompletionResult.Success -> {
|
||||
scanResponse = result.data
|
||||
userWallet = createUserWallet(scanResponse = result.data, backupState = backupState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
when (backupState.startedSource) {
|
||||
BackupStartedSource.Onboarding -> saveWallet(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWallet = userWallet,
|
||||
scanResponse = scanResponse,
|
||||
backupState = backupState,
|
||||
)
|
||||
BackupStartedSource.CreateBackup -> updateWallet(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWallet = userWallet,
|
||||
backupState = backupState,
|
||||
)
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
if (userWallet.scanResponse.cardTypesResolver.isWallet2() && userWallet.isImported) {
|
||||
store.inject(DaggerGraphState::walletsRepository).markWallet2WasCreated(userWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
val notActivatedCardIds = gatherCardIds(backupState, card).mapNotNull {
|
||||
if (store.state.globalState.onboardingState.onboardingManager?.isActivationFinished(it) == true) {
|
||||
null
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
// All cardIds may already be activated if the backup was skipped before.
|
||||
if (notActivatedCardIds.isEmpty()) {
|
||||
delay(1000)
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWallet.walletId))
|
||||
return@launch
|
||||
}
|
||||
|
||||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds)
|
||||
handleFinishBackup(requireNotNull(scanResponse), userWallet)
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet.walletId))
|
||||
}
|
||||
}
|
||||
|
||||
is BackupAction.ResetBackupCard -> {
|
||||
scope.launch { tangemSdkManager.resetToFactorySettings(action.cardId, false) }
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun Card?.isRing(): Boolean {
|
||||
return this?.let { RING_BATCH_IDS.contains(batchId) || batchId.startsWith(RING_BATCH_PREFIX) } ?: false
|
||||
}
|
||||
|
||||
private suspend fun saveWallet(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWallet: UserWallet,
|
||||
scanResponse: ScanResponse?,
|
||||
backupState: BackupState,
|
||||
) {
|
||||
userWalletsListManager.save(
|
||||
userWallet = userWallet.copy(
|
||||
scanResponse = updateScanResponseAfterBackup(
|
||||
scanResponse = requireNotNull(
|
||||
value = scanResponse,
|
||||
lazyMessage = { "ScanResponse is null" },
|
||||
),
|
||||
backupState = backupState,
|
||||
),
|
||||
),
|
||||
canOverride = true,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun updateWallet(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWallet: UserWallet,
|
||||
backupState: BackupState,
|
||||
) {
|
||||
userWalletsListManager.update(
|
||||
userWalletId = userWallet.walletId,
|
||||
update = { wallet ->
|
||||
wallet.copy(
|
||||
scanResponse = updateScanResponseAfterBackup(
|
||||
scanResponse = wallet.scanResponse,
|
||||
backupState = backupState,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: BackupState): UserWallet {
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
return requireNotNull(
|
||||
value = userWalletBuilder
|
||||
.backupCardsIds(backupState.backupCardIds.toSet())
|
||||
.hasBackupError(backupState.hasBackupError)
|
||||
.build(),
|
||||
lazyMessage = { "User wallet not created" },
|
||||
)
|
||||
}
|
||||
|
||||
fun updateArtworks(addedBackupCardsCount: Int, card: Card) {
|
||||
mainScope.launch {
|
||||
withIOContext {
|
||||
val imageUri = loadArtworkForCard(card.cardId, card.cardPublicKey, Uri.EMPTY)
|
||||
when (addedBackupCardsCount) {
|
||||
1 -> {
|
||||
store.dispatchOnMain(OnboardingWalletAction.SetSecondCardArtworkUrl(imageUri))
|
||||
}
|
||||
2 -> {
|
||||
store.dispatchOnMain(OnboardingWalletAction.SetThirdCardArtworkUrl(imageUri))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun gatherCardIds(backupState: BackupState, card: CardDTO?): List<String> {
|
||||
return (listOf(backupState.primaryCardId, card?.cardId) + backupState.backupCardIds)
|
||||
.filterNotNull()
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private fun handleOnBackPressed(state: OnboardingWalletState) {
|
||||
when (state.backupState.backupStep) {
|
||||
null, BackupStep.InitBackup, BackupStep.ScanOriginCard, BackupStep.AddBackupCards, BackupStep.EnterAccessCode,
|
||||
BackupStep.ReenterAccessCode, BackupStep.SetAccessCode, BackupStep.WritePrimaryCard,
|
||||
-> {
|
||||
showInterruptOnboardingDialog()
|
||||
}
|
||||
is BackupStep.WriteBackupCard -> {
|
||||
store.dispatch(GlobalAction.ShowDialog(BackupDialog.BackupInProgress))
|
||||
}
|
||||
BackupStep.Finished -> {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showInterruptOnboardingDialog() {
|
||||
store.dispatchDialogShow(
|
||||
OnboardingDialog.InterruptOnboarding(
|
||||
onOk = {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import com.tangem.domain.redux.OnboardingManageTokensAction
|
||||
import com.tangem.tap.backupService
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import org.rekotlin.Action
|
||||
|
||||
object OnboardingWalletReducer {
|
||||
fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state)
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState {
|
||||
val state = appState.onboardingWalletState
|
||||
val backupState = state.backupState
|
||||
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding -> ReducerForGlobalAction.reduce(action, state)
|
||||
is BackupAction.BackupFinished -> {
|
||||
state.copy(
|
||||
step = if (action.userWalletId != null && backupState.startedSource == BackupStartedSource.Onboarding) {
|
||||
OnboardingWalletStep.ManageTokens
|
||||
} else {
|
||||
OnboardingWalletStep.Done
|
||||
},
|
||||
userWalletId = action.userWalletId,
|
||||
)
|
||||
}
|
||||
is BackupAction -> state.copy(backupState = BackupReducer.reduce(action = action, state = state.backupState))
|
||||
is OnboardingWallet2Action -> OnboardingWallet2Reducer.reduce(action, state)
|
||||
is OnboardingWalletAction.GetToCreateWalletStep -> state.copy(
|
||||
step = OnboardingWalletStep.CreateWallet,
|
||||
)
|
||||
is OnboardingWalletAction.ResumeBackup -> state.copy(step = OnboardingWalletStep.Backup)
|
||||
is OnboardingWalletAction.SetPrimaryCardArtworkUrl -> {
|
||||
state.copy(
|
||||
walletImages = state.walletImages.copy(primaryCardImage = action.artworkUri),
|
||||
)
|
||||
}
|
||||
is OnboardingWalletAction.SetSecondCardArtworkUrl -> {
|
||||
state.copy(
|
||||
walletImages = state.walletImages.copy(secondCardImage = action.artworkUri),
|
||||
)
|
||||
}
|
||||
is OnboardingWalletAction.SetThirdCardArtworkUrl -> {
|
||||
state.copy(
|
||||
walletImages = state.walletImages.copy(thirdCardImage = action.artworkUri),
|
||||
)
|
||||
}
|
||||
is OnboardingManageTokensAction.CurrenciesSaved -> state.copy(step = OnboardingWalletStep.Done)
|
||||
is OnboardingWalletAction.WalletSaved -> state.copy(
|
||||
step = when (backupState.startedSource) {
|
||||
BackupStartedSource.Onboarding -> OnboardingWalletStep.ManageTokens
|
||||
BackupStartedSource.CreateBackup -> OnboardingWalletStep.Done
|
||||
},
|
||||
userWalletId = action.userWalletId,
|
||||
)
|
||||
is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private object ReducerForGlobalAction {
|
||||
|
||||
private const val MAX_BACKUP_CARDS = 2
|
||||
|
||||
fun reduce(action: GlobalAction.Onboarding, state: OnboardingWalletState): OnboardingWalletState {
|
||||
return when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
OnboardingWalletState(
|
||||
backupState = state.backupState.copy(
|
||||
maxBackupCards = MAX_BACKUP_CARDS,
|
||||
canSkipBackup = action.canSkipBackup,
|
||||
startedSource = action.source,
|
||||
),
|
||||
)
|
||||
}
|
||||
is GlobalAction.Onboarding.StartForUnfinishedBackup -> {
|
||||
OnboardingWalletState(
|
||||
backupState = state.backupState.copy(
|
||||
maxBackupCards = action.addedBackupCardsCount,
|
||||
canSkipBackup = false,
|
||||
isInterruptedBackup = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object OnboardingWallet2Reducer {
|
||||
fun reduce(action: OnboardingWallet2Action, state: OnboardingWalletState): OnboardingWalletState = when (action) {
|
||||
is OnboardingWallet2Action.SetDependencies -> state.copy(
|
||||
wallet2State = OnboardingWallet2State(
|
||||
maxProgress = action.maxProgress,
|
||||
),
|
||||
)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private object BackupReducer {
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun reduce(action: BackupAction, state: BackupState): BackupState {
|
||||
return when (action) {
|
||||
is BackupAction.IntroduceBackup -> BackupState(
|
||||
backupStep = BackupStep.InitBackup,
|
||||
maxBackupCards = 2,
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
startedSource = state.startedSource,
|
||||
)
|
||||
BackupAction.StartAddingPrimaryCard -> {
|
||||
state.copy(
|
||||
backupStep = BackupStep.ScanOriginCard,
|
||||
maxBackupCards = 2,
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
)
|
||||
}
|
||||
BackupAction.StartAddingBackupCards -> {
|
||||
state.copy(
|
||||
backupStep = BackupStep.AddBackupCards,
|
||||
maxBackupCards = 2,
|
||||
canSkipBackup = state.canSkipBackup,
|
||||
)
|
||||
}
|
||||
is BackupAction.AddBackupCard.Success -> {
|
||||
state.copy(
|
||||
backupCards = state.backupCards + action.card,
|
||||
backupCardsNumber = state.backupCardsNumber + 1,
|
||||
)
|
||||
}
|
||||
is BackupAction.AddBackupCard.ChangeButtonLoading -> state.copy(showBtnLoading = action.isLoading)
|
||||
BackupAction.ShowAccessCodeInfoScreen -> state.copy(backupStep = BackupStep.SetAccessCode)
|
||||
BackupAction.ShowEnterAccessCodeScreen -> state.copy(
|
||||
backupStep = BackupStep.EnterAccessCode,
|
||||
accessCodeError = null,
|
||||
)
|
||||
is BackupAction.SaveFirstAccessCode -> state.copy(
|
||||
backupStep = BackupStep.ReenterAccessCode,
|
||||
accessCode = action.accessCode,
|
||||
)
|
||||
is BackupAction.SetAccessCodeError -> state.copy(accessCodeError = action.error)
|
||||
is BackupAction.PrepareToWritePrimaryCard -> if (state.primaryCardId == null) {
|
||||
state.copy(
|
||||
primaryCardId = backupService.primaryCardId,
|
||||
primaryCardBatchId = backupService.primaryCardBatchId,
|
||||
backupCardIds = backupService.backupCardIds,
|
||||
backupCardBatchIds = backupService.backupCardsBatchIds,
|
||||
backupCardsNumber = backupService.backupCardIds.size,
|
||||
backupStep = BackupStep.WritePrimaryCard,
|
||||
)
|
||||
} else {
|
||||
state.copy(backupStep = BackupStep.WritePrimaryCard)
|
||||
}
|
||||
is BackupAction.PrepareToWriteBackupCard -> if (state.primaryCardId == null) {
|
||||
state.copy(
|
||||
primaryCardId = backupService.primaryCardId,
|
||||
primaryCardBatchId = backupService.primaryCardBatchId,
|
||||
backupCardIds = backupService.backupCardIds,
|
||||
backupCardBatchIds = backupService.backupCardsBatchIds,
|
||||
backupCardsNumber = backupService.backupCardIds.size,
|
||||
backupStep = BackupStep.WriteBackupCard(action.cardNumber),
|
||||
)
|
||||
} else {
|
||||
state.copy(backupStep = BackupStep.WriteBackupCard(action.cardNumber))
|
||||
}
|
||||
is BackupAction.ErrorInBackupCard -> state.copy(hasBackupError = true)
|
||||
is BackupAction.SkipBackup -> state.copy(backupStep = BackupStep.Finished)
|
||||
is BackupAction.FinishBackup -> state.copy(backupStep = BackupStep.Finished)
|
||||
BackupAction.OnAccessCodeDialogClosed -> state.copy(backupStep = BackupStep.AddBackupCards)
|
||||
BackupAction.DiscardBackup -> BackupState()
|
||||
is BackupAction.SetHasRing -> {
|
||||
// Don't update if state.has ring is already true
|
||||
if (state.hasRing) {
|
||||
state
|
||||
} else {
|
||||
state.copy(hasRing = action.hasRing)
|
||||
}
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.redux
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class OnboardingWalletState(
|
||||
val step: OnboardingWalletStep = OnboardingWalletStep.None,
|
||||
val wallet2State: OnboardingWallet2State? = null,
|
||||
val backupState: BackupState = BackupState(),
|
||||
val walletImages: WalletImages = WalletImages(),
|
||||
val showConfetti: Boolean = false,
|
||||
val isRingOnboarding: Boolean = false,
|
||||
val userWalletId: UserWalletId? = null,
|
||||
) : StateType {
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun getMaxProgress(): Int {
|
||||
val baseProgress = 7
|
||||
return getWallet2Progress() + baseProgress
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "MagicNumber")
|
||||
fun getProgressStep(): Int {
|
||||
val progressByStep = when (step) {
|
||||
OnboardingWalletStep.CreateWallet, OnboardingWalletStep.None -> 1
|
||||
OnboardingWalletStep.Backup -> {
|
||||
when (backupState.backupStep) {
|
||||
null -> 2
|
||||
BackupStep.InitBackup -> 2
|
||||
BackupStep.ScanOriginCard -> 3
|
||||
BackupStep.AddBackupCards -> 4
|
||||
BackupStep.EnterAccessCode -> 4
|
||||
BackupStep.ReenterAccessCode -> 4
|
||||
BackupStep.SetAccessCode -> 4
|
||||
BackupStep.WritePrimaryCard, is BackupStep.WriteBackupCard -> 5
|
||||
BackupStep.Finished -> 6
|
||||
}
|
||||
}
|
||||
OnboardingWalletStep.ManageTokens -> 6
|
||||
OnboardingWalletStep.Done -> getMaxProgress()
|
||||
}
|
||||
|
||||
return getWallet2Progress() + progressByStep
|
||||
}
|
||||
|
||||
private fun getWallet2Progress(): Int = wallet2State?.maxProgress ?: 0
|
||||
}
|
||||
|
||||
data class WalletImages(
|
||||
val primaryCardImage: Uri? = null,
|
||||
val secondCardImage: Uri? = null,
|
||||
val thirdCardImage: Uri? = null,
|
||||
)
|
||||
|
||||
data class OnboardingWallet2State(
|
||||
val maxProgress: Int,
|
||||
)
|
||||
|
||||
enum class OnboardingWalletStep {
|
||||
None, CreateWallet, Backup, ManageTokens, Done
|
||||
}
|
||||
|
||||
data class BackupState(
|
||||
val primaryCardId: String? = null,
|
||||
val primaryCardBatchId: String? = null,
|
||||
val backupCardsNumber: Int = 0,
|
||||
val backupCards: List<Card> = emptyList(),
|
||||
val backupCardIds: List<CardId> = emptyList(),
|
||||
val backupCardBatchIds: List<CardId> = emptyList(),
|
||||
@Transient
|
||||
val backupCardsArtworks: Map<CardId, Bitmap> = emptyMap(),
|
||||
val accessCode: String = "",
|
||||
val accessCodeError: AccessCodeError? = null,
|
||||
val backupStep: BackupStep? = null,
|
||||
val maxBackupCards: Int = 2,
|
||||
val canSkipBackup: Boolean = true,
|
||||
val isInterruptedBackup: Boolean = false,
|
||||
val showBtnLoading: Boolean = false,
|
||||
val hasBackupError: Boolean = false,
|
||||
val startedSource: BackupStartedSource = BackupStartedSource.Onboarding,
|
||||
val hasRing: Boolean = false,
|
||||
)
|
||||
|
||||
enum class BackupStartedSource {
|
||||
Onboarding, CreateBackup
|
||||
}
|
||||
|
||||
enum class AccessCodeError {
|
||||
CodeTooShort, CodesDoNotMatch
|
||||
}
|
||||
|
||||
typealias CardId = String
|
||||
|
||||
sealed class BackupStep {
|
||||
data object InitBackup : BackupStep()
|
||||
data object ScanOriginCard : BackupStep()
|
||||
data object AddBackupCards : BackupStep()
|
||||
data object SetAccessCode : BackupStep()
|
||||
data object EnterAccessCode : BackupStep()
|
||||
data object ReenterAccessCode : BackupStep()
|
||||
data object WritePrimaryCard : BackupStep()
|
||||
data class WriteBackupCard(val cardNumber: Int) : BackupStep()
|
||||
data object Finished : BackupStep()
|
||||
}
|
||||
|
||||
sealed class BackupDialog : StateDialog {
|
||||
data object AttestationFailed : BackupDialog()
|
||||
data object AddMoreBackupCards : BackupDialog()
|
||||
data object BackupInProgress : BackupDialog()
|
||||
|
||||
data class UnfinishedBackupFound(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : BackupDialog()
|
||||
|
||||
data class ConfirmDiscardingBackup(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : BackupDialog()
|
||||
|
||||
data class ResetBackupCard(val cardId: String) : BackupDialog()
|
||||
}
|
||||
|
|
@ -1,314 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.common.postUiDelayBg
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.BackupCardType.*
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface BackupAnimator {
|
||||
fun updateBackupState(backupState: BackupState)
|
||||
fun setupCreateWalletState()
|
||||
fun showBackupIntro(state: BackupState)
|
||||
fun showScanOriginCard()
|
||||
fun showAddBackupCards(state: BackupState, backupCardsCounts: Int)
|
||||
fun showWritePrimaryCard(state: BackupState)
|
||||
fun showWriteBackupCard(state: BackupState, backupCard: Int)
|
||||
fun showSuccess(onEnd: () -> Unit)
|
||||
}
|
||||
|
||||
class WalletBackupAnimator(
|
||||
private val cardsWidget: WalletCardsWidget,
|
||||
) : BackupAnimator {
|
||||
|
||||
private val viewState: State = when (cardsWidget.leapfrogWidget.getViewsCount()) {
|
||||
2 -> State.TwoCards
|
||||
3 -> State.ThreeCards
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private var currentStep = -1
|
||||
private var firstBackupCardAnimated = false
|
||||
private var secondBackupCardAnimated = false
|
||||
|
||||
override fun updateBackupState(backupState: BackupState) {
|
||||
}
|
||||
|
||||
override fun showBackupIntro(state: BackupState) {
|
||||
currentStep = STEP_BACKUP_INTRO
|
||||
|
||||
ORIGIN.show()
|
||||
FIRST_BACKUP.show()
|
||||
SECOND_BACKUP.show()
|
||||
|
||||
cardsWidget.toWelcome()
|
||||
}
|
||||
|
||||
override fun setupCreateWalletState() {
|
||||
currentStep = STEP_CREATE_WALLET
|
||||
|
||||
cardsWidget.toFolded(false)
|
||||
|
||||
ORIGIN.alpha(1f)
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
SECOND_BACKUP.alpha(1f)
|
||||
}
|
||||
|
||||
override fun showScanOriginCard() {
|
||||
currentStep = STEP_SCAN_ORIGIN_CARD
|
||||
|
||||
cardsWidget.toFolded()
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) {
|
||||
when (backupCardsCounts) {
|
||||
0 -> {
|
||||
when (currentStep) {
|
||||
UNDEFINED, STEP_CREATE_WALLET -> {
|
||||
cardsWidget.toFolded(false) { cardsWidget.toFan() }
|
||||
|
||||
FIRST_BACKUP.animateAlpha(0.6f, 0)
|
||||
SECOND_BACKUP.animateAlpha(0.2f, 400)
|
||||
}
|
||||
STEP_BACKUP_INTRO -> {
|
||||
cardsWidget.toFolded(duration = 300) { cardsWidget.toFan() }
|
||||
FIRST_BACKUP.animateAlpha(0.6f, 400, 250)
|
||||
SECOND_BACKUP.animateAlpha(0.2f, 400, 250)
|
||||
}
|
||||
STEP_SCAN_ORIGIN_CARD -> {
|
||||
cardsWidget.toFan()
|
||||
FIRST_BACKUP.animateAlpha(0.6f, 400, 250)
|
||||
SECOND_BACKUP.animateAlpha(0.2f, 400, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
cardsWidget.toFan(false)
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
SECOND_BACKUP.alpha(0.2f)
|
||||
}
|
||||
2 -> {
|
||||
cardsWidget.toFan(false)
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
SECOND_BACKUP.alpha(1f)
|
||||
}
|
||||
}
|
||||
|
||||
currentStep = STEP_ADD_BACKUP_CARDS
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun showWritePrimaryCard(state: BackupState) {
|
||||
currentStep = STEP_WRITE_PRIMARY_CARD
|
||||
|
||||
cardsWidget.toLeapfrog(
|
||||
onEndFold = {
|
||||
FIRST_BACKUP.alpha(0.4f)
|
||||
SECOND_BACKUP.alpha(0.2f)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun showWriteBackupCard(state: BackupState, backupCard: Int) {
|
||||
when (viewState) {
|
||||
State.TwoCards -> {
|
||||
if (firstBackupCardAnimated) return
|
||||
firstBackupCardAnimated = true
|
||||
|
||||
val delay = if (currentStep == UNDEFINED) 700L else 0
|
||||
postUiDelayBg(delay) {
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
cardsWidget.leapfrogWidget.leap {
|
||||
ORIGIN.alpha(0.4f)
|
||||
}
|
||||
}
|
||||
}
|
||||
State.ThreeCards -> {
|
||||
when (backupCard) {
|
||||
1 -> {
|
||||
if (firstBackupCardAnimated) return
|
||||
firstBackupCardAnimated = true
|
||||
|
||||
FIRST_BACKUP.alpha(1f)
|
||||
cardsWidget.leapfrogWidget.leap {
|
||||
SECOND_BACKUP.alpha(0.4f)
|
||||
ORIGIN.alpha(0.2f)
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
if (secondBackupCardAnimated) return
|
||||
secondBackupCardAnimated = true
|
||||
|
||||
/*
|
||||
* !!! Workaround !!!
|
||||
* For interrupted backup
|
||||
* */
|
||||
if (!firstBackupCardAnimated) {
|
||||
showWriteBackupCard(state, backupCard = 1)
|
||||
return
|
||||
}
|
||||
|
||||
SECOND_BACKUP.alpha(1f)
|
||||
cardsWidget.leapfrogWidget.leap {
|
||||
ORIGIN.alpha(0.4f)
|
||||
FIRST_BACKUP.alpha(0.2f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
currentStep = STEP_WRITE_BACKUP_CARD
|
||||
}
|
||||
|
||||
override fun showSuccess(onEnd: () -> Unit) {
|
||||
currentStep = STEP_SUCCESS
|
||||
cardsWidget.leapfrogWidget.fold(onEnd = onEnd)
|
||||
}
|
||||
|
||||
private enum class State {
|
||||
ThreeCards,
|
||||
TwoCards,
|
||||
}
|
||||
|
||||
private fun BackupCardType.animateAlpha(alpha: Float, duration: Long = 0L, startDelay: Long = 0L) {
|
||||
when (this) {
|
||||
ORIGIN -> {
|
||||
cardsWidget.getOriginCardView().animate().apply {
|
||||
alpha(alpha)
|
||||
this.startDelay = startDelay
|
||||
this.duration = duration
|
||||
}
|
||||
}
|
||||
FIRST_BACKUP -> {
|
||||
cardsWidget.getFirstBackupCardView().animate().apply {
|
||||
alpha(alpha)
|
||||
this.startDelay = startDelay
|
||||
this.duration = duration
|
||||
}
|
||||
}
|
||||
SECOND_BACKUP -> {
|
||||
if (viewState != State.ThreeCards) return
|
||||
cardsWidget.getSecondBackupCardView().animate().apply {
|
||||
alpha(alpha)
|
||||
this.startDelay = startDelay
|
||||
this.duration = duration
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BackupCardType.alpha(alpha: Float) {
|
||||
when (this) {
|
||||
ORIGIN -> cardsWidget.getOriginCardView().alpha = alpha
|
||||
FIRST_BACKUP -> cardsWidget.getFirstBackupCardView().alpha = alpha
|
||||
SECOND_BACKUP -> {
|
||||
if (viewState != State.ThreeCards) return
|
||||
cardsWidget.getSecondBackupCardView().alpha = alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BackupCardType.show() {
|
||||
when (this) {
|
||||
ORIGIN -> cardsWidget.getOriginCardView().show()
|
||||
FIRST_BACKUP -> cardsWidget.getFirstBackupCardView().show()
|
||||
SECOND_BACKUP -> {
|
||||
if (viewState != State.ThreeCards) return
|
||||
cardsWidget.getSecondBackupCardView().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val UNDEFINED = -1
|
||||
private const val STEP_CREATE_WALLET = 0
|
||||
private const val STEP_BACKUP_INTRO = 1
|
||||
private const val STEP_SCAN_ORIGIN_CARD = 2
|
||||
private const val STEP_ADD_BACKUP_CARDS = 3
|
||||
private const val STEP_WRITE_PRIMARY_CARD = 4
|
||||
private const val STEP_WRITE_BACKUP_CARD = 5
|
||||
private const val STEP_SUCCESS = 6
|
||||
}
|
||||
}
|
||||
|
||||
class TestBackupAnimation(
|
||||
private val animator: BackupAnimator,
|
||||
binding: FragmentOnboardingWalletBinding,
|
||||
) : BackupAnimator {
|
||||
|
||||
private lateinit var state: BackupState
|
||||
private var steps = 0
|
||||
|
||||
init {
|
||||
init(binding, this)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun setStep(step: Int, onStepUpdate: (Int) -> Unit = {}) {
|
||||
steps = step
|
||||
when (steps) {
|
||||
0 -> setupCreateWalletState()
|
||||
1 -> showBackupIntro(state)
|
||||
2 -> showScanOriginCard()
|
||||
3 -> showAddBackupCards(state, 0)
|
||||
4 -> showAddBackupCards(state, 1)
|
||||
5 -> showAddBackupCards(state, 2)
|
||||
6 -> showWritePrimaryCard(state)
|
||||
7 -> showWriteBackupCard(state, 1)
|
||||
8 -> showWriteBackupCard(state, 2)
|
||||
else -> setStep(0)
|
||||
}
|
||||
onStepUpdate(steps)
|
||||
}
|
||||
|
||||
fun onNext(onStepUpdate: (Int) -> Unit) {
|
||||
steps++
|
||||
setStep(steps, onStepUpdate)
|
||||
}
|
||||
|
||||
override fun updateBackupState(backupState: BackupState) {
|
||||
this.state = backupState
|
||||
}
|
||||
|
||||
override fun setupCreateWalletState() {
|
||||
animator.setupCreateWalletState()
|
||||
}
|
||||
|
||||
override fun showBackupIntro(state: BackupState) {
|
||||
animator.showBackupIntro(state)
|
||||
}
|
||||
|
||||
override fun showScanOriginCard() {
|
||||
animator.showScanOriginCard()
|
||||
}
|
||||
|
||||
override fun showAddBackupCards(state: BackupState, backupCardsCounts: Int) {
|
||||
animator.showAddBackupCards(state, backupCardsCounts)
|
||||
}
|
||||
|
||||
override fun showWritePrimaryCard(state: BackupState) {
|
||||
animator.showWritePrimaryCard(state)
|
||||
}
|
||||
|
||||
override fun showWriteBackupCard(state: BackupState, backupCard: Int) {
|
||||
animator.showWriteBackupCard(state, backupCard)
|
||||
}
|
||||
|
||||
override fun showSuccess(onEnd: () -> Unit) {
|
||||
animator.showSuccess(onEnd)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun init(binding: FragmentOnboardingWalletBinding, animator: TestBackupAnimation) {
|
||||
val button = MaterialButton(binding.onboardingRoot.context).apply { text = "Next" }
|
||||
button.setOnClickListener { animator.onNext { button.text = "$it" } }
|
||||
binding.onboardingRoot.addView(button, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tap.common.extensions.getString
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.ItemBackupInfoAdapterBinding
|
||||
|
||||
class BackupInfoAdapter : RecyclerView.Adapter<BackupInfoViewHolder>() {
|
||||
|
||||
private val data = backupInfoSnippets
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupInfoViewHolder = BackupInfoViewHolder(
|
||||
ItemBackupInfoAdapterBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false,
|
||||
),
|
||||
)
|
||||
|
||||
override fun getItemCount(): Int = data.size
|
||||
|
||||
override fun onBindViewHolder(holder: BackupInfoViewHolder, position: Int) = holder.binding.run {
|
||||
tvHeader.text = root.getString(data[position].header)
|
||||
tvBody.text = root.getString(data[position].body)
|
||||
}
|
||||
}
|
||||
|
||||
private class BackupInfo(
|
||||
@StringRes val header: Int,
|
||||
@StringRes val body: Int,
|
||||
)
|
||||
|
||||
private val backupInfoSnippets = listOf(
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_first,
|
||||
R.string.onboarding_wallet_info_subtitle_first,
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_second,
|
||||
R.string.onboarding_wallet_info_subtitle_second,
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_third,
|
||||
R.string.onboarding_wallet_info_subtitle_third,
|
||||
),
|
||||
BackupInfo(
|
||||
R.string.onboarding_wallet_info_title_fourth,
|
||||
R.string.onboarding_wallet_info_subtitle_fourth,
|
||||
),
|
||||
|
||||
)
|
||||
|
||||
class BackupInfoViewHolder(val binding: ItemBackupInfoAdapterBinding) :
|
||||
RecyclerView.ViewHolder(binding.root)
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.windowsize.rememberWindowSize
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseApi
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletStep
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class OnboardingSeedPhraseStateHandler(
|
||||
private val onboardingSeedPhraseApi: OnboardingSeedPhraseApi = OnboardingSeedPhraseScreen(),
|
||||
private val activity: Activity,
|
||||
) {
|
||||
|
||||
fun newState(
|
||||
walletFragment: OnboardingWalletFragment,
|
||||
state: OnboardingWalletState,
|
||||
seedPhraseViewModel: SeedPhraseViewModel,
|
||||
) {
|
||||
if (state.step == OnboardingWalletStep.CreateWallet && !seedPhraseViewModel.isFinished) {
|
||||
switchToWallet2SeedPhraseOnboarding(walletFragment, seedPhraseViewModel, state.getMaxProgress())
|
||||
} else {
|
||||
switchToWalletOnboarding(walletFragment, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun switchToWalletOnboarding(walletFragment: OnboardingWalletFragment, state: OnboardingWalletState) {
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.hide()
|
||||
walletFragment.pbBinding.pbState.show()
|
||||
walletFragment.binding.onboardingWalletContainer.show()
|
||||
walletFragment.handleOnboardingStep(state)
|
||||
}
|
||||
|
||||
private fun switchToWallet2SeedPhraseOnboarding(
|
||||
walletFragment: OnboardingWalletFragment,
|
||||
viewModel: SeedPhraseViewModel,
|
||||
onboardingWalletMaxProgress: Int,
|
||||
) {
|
||||
walletFragment.pbBinding.pbState.hide()
|
||||
walletFragment.binding.onboardingWalletContainer.hide()
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.show()
|
||||
walletFragment.bindingSeedPhrase.onboardingSeedPhraseContainer.setContent {
|
||||
val subScreen = viewModel.currentScreen.collectAsState().value
|
||||
setMainScreenToolbarTitle(walletFragment, subScreen)
|
||||
|
||||
TangemTheme(
|
||||
isDark = isSystemInDarkTheme(),
|
||||
windowSize = rememberWindowSize(activity = activity),
|
||||
) {
|
||||
onboardingSeedPhraseApi.ScreenContent(
|
||||
uiState = viewModel.uiState,
|
||||
subScreen = subScreen,
|
||||
progress = viewModel.progress.collectAsState(0).value.toFloat() / onboardingWalletMaxProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMainScreenToolbarTitle(walletFragment: OnboardingWalletFragment, subScreen: SeedPhraseScreen) {
|
||||
val titleResId = when (subScreen) {
|
||||
SeedPhraseScreen.Intro -> R.string.wallet_title
|
||||
SeedPhraseScreen.AboutSeedPhrase,
|
||||
SeedPhraseScreen.YourSeedPhrase,
|
||||
SeedPhraseScreen.CheckSeedPhrase,
|
||||
-> R.string.onboarding_create_wallet_header
|
||||
SeedPhraseScreen.ImportSeedPhrase -> R.string.onboarding_seed_intro_button_import
|
||||
}
|
||||
|
||||
walletFragment.binding.toolbar.title = walletFragment.getString(titleResId)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,691 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.transition.TransitionManager
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import coil.load
|
||||
import com.arkivanov.decompose.defaultComponentContext
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.di.RootAppComponentContext
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.windowsize.rememberWindowSize
|
||||
import com.tangem.datasource.utils.isNullOrEmpty
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.scan.isRing
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel
|
||||
import com.tangem.features.managetokens.component.OnboardingManageTokensComponent
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.features.BaseFragment
|
||||
import com.tangem.tap.features.FragmentOnBackPressedHandler
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
||||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LargeClass", "MagicNumber")
|
||||
@AndroidEntryPoint
|
||||
class OnboardingWalletFragment :
|
||||
BaseFragment(R.layout.fragment_onboarding_wallet),
|
||||
StoreSubscriber<OnboardingWalletState>,
|
||||
FragmentOnBackPressedHandler {
|
||||
|
||||
@Inject
|
||||
internal lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Inject
|
||||
internal lateinit var onboardingManageTokensComponentFactory: OnboardingManageTokensComponent.Factory
|
||||
|
||||
@Inject
|
||||
@RootAppComponentContext
|
||||
internal lateinit var rootComponentContext: AppComponentContext
|
||||
|
||||
private var onboardingManageTokensComponent: OnboardingManageTokensComponent? = null
|
||||
private lateinit var onboardingComponentContext: AppComponentContext
|
||||
|
||||
internal val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind)
|
||||
internal val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind)
|
||||
|
||||
internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer }
|
||||
private val bindingManageTokens by lazy { binding.onboardingManageTokensContainer }
|
||||
|
||||
private val canSkipBackup by lazy { arguments?.getBoolean(AppRoute.OnboardingWallet.CAN_SKIP_BACKUP_KEY) ?: true }
|
||||
|
||||
private lateinit var seedPhraseStateHandler: OnboardingSeedPhraseStateHandler
|
||||
|
||||
private val seedPhraseViewModel by viewModels<SeedPhraseViewModel>()
|
||||
|
||||
private lateinit var cardsWidget: WalletCardsWidget
|
||||
private var seedPhraseRouter: SeedPhraseRouter? = null
|
||||
private var accessCodeDialog: AccessCodeDialog? = null
|
||||
|
||||
private lateinit var animator: BackupAnimator
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
seedPhraseStateHandler = OnboardingSeedPhraseStateHandler(activity = requireActivity())
|
||||
|
||||
val newSeedPhraseRouter = makeSeedPhraseRouter()
|
||||
seedPhraseRouter = newSeedPhraseRouter
|
||||
seedPhraseViewModel.setRouter(newSeedPhraseRouter)
|
||||
seedPhraseViewModel.setMediator(makeSeedPhraseMediator())
|
||||
|
||||
onboardingComponentContext = rootComponentContext.childByContext(
|
||||
componentContext = defaultComponentContext(onBackPressedDispatcher = null),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val scaleFactor = getDeviceScaleFactor()
|
||||
initCardsWidget(createLeapfrogWidget(binding.flCardsContainer, scaleFactor), scaleFactor)
|
||||
|
||||
binding.viewPagerBackupInfo.adapter = BackupInfoAdapter()
|
||||
TabLayoutMediator(
|
||||
binding.tabLayoutBackupInfo,
|
||||
binding.viewPagerBackupInfo,
|
||||
) { tab, position ->
|
||||
// Some implementation
|
||||
}.attach()
|
||||
|
||||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
|
||||
addBackPressHandler(handler = this)
|
||||
|
||||
store.dispatch(OnboardingWallet2Action.Init(seedPhraseViewModel.maxProgress))
|
||||
store.dispatch(OnboardingWalletAction.Init)
|
||||
store.dispatch(
|
||||
OnboardingWalletAction.LoadArtwork(
|
||||
cardArtworkUriForUnfinishedBackup = requireContext().resourceUri(R.drawable.card_placeholder_wallet),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) {
|
||||
val viewBackupCount = flCardsContainer.childCount - 1
|
||||
if (viewBackupCount <= 0) return@with
|
||||
if (viewBackupCount == backupCardsCounts) return@with
|
||||
|
||||
cardsWidget.toFolded(false)
|
||||
|
||||
if (viewBackupCount > backupCardsCounts) {
|
||||
flCardsContainer.removeViews(0, viewBackupCount - backupCardsCounts)
|
||||
} else {
|
||||
flCardsContainer.inflate(R.layout.view_onboarding_card, true)
|
||||
}
|
||||
|
||||
val scaleFactor = getDeviceScaleFactor()
|
||||
initCardsWidget(createLeapfrogWidget(flCardsContainer, scaleFactor), scaleFactor)
|
||||
}
|
||||
|
||||
private fun createLeapfrogWidget(container: FrameLayout, deviceScaleFactor: Float): LeapfrogWidget {
|
||||
val leapfrogCalculator = PropertyCalculator(
|
||||
yTranslationFactor = when (container.childCount) {
|
||||
3 -> 25f
|
||||
else -> 35f
|
||||
} * deviceScaleFactor,
|
||||
)
|
||||
return LeapfrogWidget(container, leapfrogCalculator)
|
||||
}
|
||||
|
||||
private fun initCardsWidget(leapfrogWidget: LeapfrogWidget, deviceScaleFactor: Float, isTest: Boolean = false) {
|
||||
cardsWidget = WalletCardsWidget(leapfrogWidget, deviceScaleFactor)
|
||||
animator = if (isTest) {
|
||||
TestBackupAnimation(WalletBackupAnimator(cardsWidget), binding)
|
||||
} else {
|
||||
WalletBackupAnimator(cardsWidget)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.onboardingWalletState == newState.onboardingWalletState
|
||||
}.select { it.onboardingWalletState }
|
||||
}
|
||||
setStatusBarColor(R.color.background_primary)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
onboardingManageTokensComponent = null
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: OnboardingWalletState) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (activity == null || view == null) return@post
|
||||
animator.updateBackupState(state.backupState)
|
||||
requireActivity().invalidateOptionsMenu()
|
||||
|
||||
pbBinding.pbState.max = state.getMaxProgress()
|
||||
pbBinding.pbState.progress = state.getProgressStep()
|
||||
|
||||
when {
|
||||
state.wallet2State != null -> {
|
||||
seedPhraseStateHandler.newState(this, state, seedPhraseViewModel)
|
||||
seedPhraseViewModel.setCardArtworkUri(
|
||||
cardArtworkUri = state.walletImages.primaryCardImage.toString(),
|
||||
)
|
||||
updateWalletImagesState(state.walletImages)
|
||||
}
|
||||
else -> {
|
||||
updateWalletImagesState(state.walletImages)
|
||||
handleOnboardingStep(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWalletImagesState(walletImages: WalletImages) {
|
||||
if (!walletImages.primaryCardImage.isNullOrEmpty()) {
|
||||
loadImageIntoImageView(walletImages.primaryCardImage, binding.imvFrontCard)
|
||||
}
|
||||
if (!walletImages.secondCardImage.isNullOrEmpty()) {
|
||||
loadImageIntoImageView(walletImages.secondCardImage, binding.imvFirstBackupCard)
|
||||
}
|
||||
if (!walletImages.thirdCardImage.isNullOrEmpty()) {
|
||||
loadImageIntoImageView(walletImages.thirdCardImage, binding.imvSecondBackupCard)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadImageIntoImageView(uri: Uri?, view: ImageView) {
|
||||
view.load(uri) {
|
||||
placeholder(R.drawable.card_placeholder_black)
|
||||
error(R.drawable.card_placeholder_black)
|
||||
fallback(R.drawable.card_placeholder_black)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun handleOnboardingStep(state: OnboardingWalletState) {
|
||||
when (state.step) {
|
||||
OnboardingWalletStep.CreateWallet -> setupCreateWalletState()
|
||||
OnboardingWalletStep.Backup -> setBackupState(state = state.backupState)
|
||||
OnboardingWalletStep.ManageTokens -> setManageTokensState(requireNotNull(state.userWalletId))
|
||||
OnboardingWalletStep.Done -> showSuccess()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeOnboardingManageTokensComponent(userWalletId: UserWalletId) {
|
||||
onboardingManageTokensComponent = onboardingManageTokensComponentFactory.create(
|
||||
context = onboardingComponentContext,
|
||||
params = OnboardingManageTokensComponent.Params(userWalletId = userWalletId),
|
||||
onDone = {},
|
||||
)
|
||||
}
|
||||
|
||||
private fun setManageTokensState(userWalletId: UserWalletId) {
|
||||
with(binding) {
|
||||
tvHeader.show()
|
||||
tvBody.show()
|
||||
viewPagerBackupInfo.hide()
|
||||
tabLayoutBackupInfo.hide()
|
||||
onboardingWalletContainer.show()
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
onboardingWalletContainer.hide()
|
||||
toolbar.title = getText(R.string.onboarding_add_tokens)
|
||||
}
|
||||
pbBinding.pbState.show()
|
||||
bindingSeedPhrase.onboardingSeedPhraseContainer.hide()
|
||||
if (onboardingManageTokensComponent == null) {
|
||||
initializeOnboardingManageTokensComponent(userWalletId)
|
||||
}
|
||||
with(bindingManageTokens) {
|
||||
onboardingManageTokensContainer.hide()
|
||||
onboardingManageTokensContainer.show()
|
||||
onboardingManageTokensContainer.setContent {
|
||||
TangemTheme(
|
||||
isDark = isSystemInDarkTheme(),
|
||||
windowSize = rememberWindowSize(activity = requireActivity()),
|
||||
) {
|
||||
onboardingManageTokensComponent?.Content(modifier = Modifier.fillMaxSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCreateWalletState() = with(binding) {
|
||||
layoutButtonsCommon.btnWalletMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet)
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
Analytics.send(Onboarding.CreateWallet.ButtonCreateWallet())
|
||||
store.dispatch(OnboardingWalletAction.CreateWallet)
|
||||
}
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
|
||||
toolbar.title = getText(R.string.onboarding_getting_started)
|
||||
|
||||
tvHeader.setText(R.string.onboarding_create_wallet_header)
|
||||
tvBody.setText(R.string.onboarding_create_wallet_body)
|
||||
|
||||
animator.setupCreateWalletState()
|
||||
}
|
||||
|
||||
private fun setBackupState(state: BackupState) {
|
||||
when (state.backupStep) {
|
||||
null -> Unit
|
||||
BackupStep.InitBackup -> showBackupIntro(state)
|
||||
BackupStep.ScanOriginCard -> showScanOriginCard()
|
||||
BackupStep.AddBackupCards -> showAddBackupCards(state)
|
||||
BackupStep.SetAccessCode -> showSetAccessCode()
|
||||
BackupStep.EnterAccessCode -> showEnterAccessCode(state)
|
||||
BackupStep.ReenterAccessCode -> showReenterAccessCode(state)
|
||||
is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state)
|
||||
is BackupStep.WriteBackupCard -> showWriteBackupCard(state)
|
||||
BackupStep.Finished -> {
|
||||
// don't need to navigate here"
|
||||
// showSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBackupIntro(state: BackupState) = with(binding) {
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
|
||||
tvHeader.hide()
|
||||
tvBody.hide()
|
||||
viewPagerBackupInfo.show()
|
||||
tabLayoutBackupInfo.show()
|
||||
|
||||
with(layoutButtonsCommon) {
|
||||
btnWalletMainAction.text = getText(R.string.onboarding_button_backup_now)
|
||||
btnWalletMainAction.icon = null
|
||||
btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.StartBackup) }
|
||||
|
||||
btnWalletAlternativeAction.text = getText(R.string.onboarding_button_skip_backup)
|
||||
btnWalletAlternativeAction.setOnClickListener { store.dispatch(BackupAction.SkipBackup) }
|
||||
btnWalletAlternativeAction.show(state.canSkipBackup && canSkipBackup)
|
||||
}
|
||||
animator.showBackupIntro(state)
|
||||
}
|
||||
|
||||
private fun showScanOriginCard() = with(binding) {
|
||||
prepareBackupView()
|
||||
tvHeader.text = getText(R.string.onboarding_title_scan_origin_card)
|
||||
tvBody.text = getString(
|
||||
R.string.onboarding_subtitle_scan_primary,
|
||||
)
|
||||
|
||||
with(layoutButtonsCommon) {
|
||||
btnWalletMainAction.text = getString(R.string.onboarding_button_scan_origin_card)
|
||||
btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
btnWalletAlternativeAction.hide()
|
||||
btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.ScanPrimaryCard) }
|
||||
}
|
||||
|
||||
animator.showScanOriginCard()
|
||||
}
|
||||
|
||||
private fun showAddBackupCards(state: BackupState) = with(binding) {
|
||||
prepareBackupView()
|
||||
|
||||
accessCodeDialog?.dismiss()
|
||||
accessCodeDialog = null
|
||||
|
||||
layoutButtonsAddCards.root.show()
|
||||
layoutButtonsCommon.root.hide()
|
||||
layoutButtonsAddCards.btnAddCard.setIconResource(R.drawable.ic_tangem_24)
|
||||
if (state.showBtnLoading) {
|
||||
layoutButtonsAddCards.btnAddCard.iconTint = ColorStateList.valueOf(
|
||||
resources.getColor(R.color.button_secondary),
|
||||
)
|
||||
layoutButtonsAddCards.btnAddCard.text = ""
|
||||
layoutButtonsAddCards.btnAddCard.isEnabled = false
|
||||
layoutButtonsAddCards.btnProgress.show()
|
||||
} else {
|
||||
layoutButtonsAddCards.btnAddCard.iconTint = ColorStateList.valueOf(
|
||||
resources.getColor(R.color.icon_primary_1),
|
||||
)
|
||||
layoutButtonsAddCards.btnProgress.hide()
|
||||
layoutButtonsAddCards.btnAddCard.text = getText(R.string.onboarding_button_add_backup_card)
|
||||
layoutButtonsAddCards.btnAddCard.isEnabled = true
|
||||
}
|
||||
if (state.backupCardsNumber < state.maxBackupCards) {
|
||||
layoutButtonsAddCards.btnAddCard.setOnClickListener {
|
||||
store.dispatch(BackupAction.AddBackupCard)
|
||||
}
|
||||
} else {
|
||||
layoutButtonsAddCards.btnAddCard.isEnabled = false
|
||||
}
|
||||
|
||||
layoutButtonsAddCards.btnContinue.text = getText(R.string.onboarding_button_finalize_backup)
|
||||
layoutButtonsAddCards.btnContinue.setOnClickListener { store.dispatch(BackupAction.FinishAddingBackupCards) }
|
||||
layoutButtonsAddCards.btnContinue.isEnabled = state.backupCardsNumber != 0
|
||||
when (state.backupCardsNumber) {
|
||||
0 -> {
|
||||
tvHeader.text = getText(R.string.onboarding_title_no_backup_cards)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_no_backup_cards)
|
||||
}
|
||||
1 -> {
|
||||
tvHeader.text = getText(R.string.onboarding_title_one_backup_card)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_one_backup_card)
|
||||
}
|
||||
2 -> {
|
||||
tvHeader.text = getText(R.string.onboarding_title_two_backup_cards)
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_two_backup_cards)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
animator.showAddBackupCards(state, state.backupCardsNumber)
|
||||
}
|
||||
|
||||
private fun prepareBackupView() = with(binding) {
|
||||
toolbar.title = getText(R.string.onboarding_navbar_title_creating_backup)
|
||||
|
||||
tvHeader.show()
|
||||
tvBody.show()
|
||||
viewPagerBackupInfo.hide()
|
||||
tabLayoutBackupInfo.hide()
|
||||
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
}
|
||||
|
||||
private fun showSetAccessCode() {
|
||||
accessCodeDialog = AccessCodeDialog(requireContext()).apply {
|
||||
dismissWithAnimation = true
|
||||
create()
|
||||
setOnCancelListener {
|
||||
store.dispatch(BackupAction.OnAccessCodeDialogClosed)
|
||||
}
|
||||
|
||||
setOnShowListener { setSecurityMode(true) }
|
||||
|
||||
setOnDismissListener { setSecurityMode(false) }
|
||||
|
||||
show()
|
||||
showInfoScreen()
|
||||
val view =
|
||||
delegate.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
|
||||
(view?.parent as? ViewGroup)?.let { TransitionManager.beginDelayedTransition(it) }
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
view?.let { it.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT }
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEnterAccessCode(state: BackupState) {
|
||||
accessCodeDialog?.showEnterAccessCode()
|
||||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
}
|
||||
|
||||
private fun showReenterAccessCode(state: BackupState) {
|
||||
accessCodeDialog?.showReenterAccessCode()
|
||||
accessCodeDialog?.showError(state.accessCodeError)
|
||||
}
|
||||
|
||||
private fun showWritePrimaryCard(state: BackupState) = with(binding) {
|
||||
accessCodeDialog?.dismiss()
|
||||
|
||||
reInitCardsWidgetIfNeeded(state.backupCardsNumber)
|
||||
prepareViewForFinalizeStep()
|
||||
|
||||
val isRing = state.primaryCardBatchId != null && isRing(batchId = state.primaryCardBatchId)
|
||||
|
||||
tvHeader.text = getText(
|
||||
if (isRing) R.string.common_origin_ring else R.string.common_origin_card,
|
||||
)
|
||||
|
||||
tvBody.text = if (isRing) {
|
||||
getString(R.string.onboarding_subtitle_scan_ring)
|
||||
} else {
|
||||
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
|
||||
getString(
|
||||
R.string.onboarding_subtitle_scan_primary_card_format,
|
||||
state.primaryCardId?.let { cardIdFormatter.getFormattedCardId(it) },
|
||||
)
|
||||
}
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getText(
|
||||
if (isRing) R.string.onboarding_button_backup_ring else R.string.onboarding_button_backup_origin,
|
||||
)
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener { store.dispatch(BackupAction.WritePrimaryCard) }
|
||||
|
||||
animator.showWritePrimaryCard(state)
|
||||
}
|
||||
|
||||
private fun prepareViewForFinalizeStep() = with(binding) {
|
||||
toolbar.title = getText(R.string.onboarding_button_finalize_backup)
|
||||
|
||||
layoutButtonsAddCards.root.hide()
|
||||
layoutButtonsCommon.root.show()
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
|
||||
imvCardBackground.hide()
|
||||
imvFirstBackupCard.show()
|
||||
imvSecondBackupCard.show()
|
||||
}
|
||||
|
||||
private fun showWriteBackupCard(state: BackupState) = with(binding) {
|
||||
reInitCardsWidgetIfNeeded(state.backupCardsNumber)
|
||||
prepareViewForFinalizeStep()
|
||||
|
||||
val cardNumber = (state.backupStep as? BackupStep.WriteBackupCard)?.cardNumber ?: 1
|
||||
|
||||
val isRing = isRing(batchId = state.backupCardBatchIds[cardNumber - 1])
|
||||
tvHeader.text = getString(
|
||||
if (isRing) R.string.onboarding_title_backup_ring else R.string.onboarding_title_backup_card,
|
||||
)
|
||||
|
||||
tvBody.text = if (isRing) {
|
||||
getString(R.string.onboarding_subtitle_scan_ring)
|
||||
} else {
|
||||
val cardIdFormatter = CardIdFormatter(CardIdDisplayFormat.LastMasked(4))
|
||||
getString(
|
||||
R.string.onboarding_subtitle_scan_backup_card_format,
|
||||
cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]),
|
||||
)
|
||||
}
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getString(
|
||||
if (isRing) R.string.onboarding_button_backup_ring else R.string.onboarding_button_backup_origin,
|
||||
)
|
||||
|
||||
layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24)
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
store.dispatch(BackupAction.WriteBackupCard(cardNumber))
|
||||
}
|
||||
|
||||
animator.showWriteBackupCard(state, cardNumber)
|
||||
}
|
||||
|
||||
private fun showSuccess() = with(binding) {
|
||||
toolbar.title = getString(R.string.onboarding_done_header)
|
||||
tvHeader.text = getText(R.string.onboarding_done_header)
|
||||
|
||||
tvHeader.show()
|
||||
tvBody.show()
|
||||
viewPagerBackupInfo.hide()
|
||||
tabLayoutBackupInfo.hide()
|
||||
onboardingWalletContainer.show()
|
||||
bindingManageTokens.onboardingManageTokensContainer.hide()
|
||||
|
||||
tvBody.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding)
|
||||
layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_continue_wallet)
|
||||
layoutButtonsCommon.btnWalletMainAction.icon = null
|
||||
layoutButtonsCommon.btnWalletAlternativeAction.hide()
|
||||
layoutButtonsCommon.btnWalletMainAction.setOnClickListener {
|
||||
showConfetti(false)
|
||||
store.dispatch(OnboardingWalletAction.FinishOnboarding(scope = requireActivity().lifecycleScope))
|
||||
}
|
||||
|
||||
animator.showSuccess {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
flCardsContainer.hide()
|
||||
imvCardBackground.hide()
|
||||
showConfetti(true)
|
||||
imvSuccess.alpha = 0f
|
||||
imvSuccess.show()
|
||||
imvSuccess.animate()?.alpha(1f)?.duration = 400
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun showConfetti(show: Boolean) {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
with(binding.vConfetti) {
|
||||
lavConfetti.show(show)
|
||||
|
||||
if (show) {
|
||||
lavConfetti.playAnimation()
|
||||
} else {
|
||||
lavConfetti.cancelAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
// workaround to use right navigation back for toolbar back btn on seed phrase flow
|
||||
val isWallet2 =
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse?.cardTypesResolver?.isWallet2()
|
||||
?: false
|
||||
val seedPhraseRouter = seedPhraseRouter
|
||||
if (seedPhraseRouter != null && isWallet2) {
|
||||
seedPhraseRouter.navigateBack()
|
||||
} else {
|
||||
legacyOnBackHandler()
|
||||
}
|
||||
}
|
||||
|
||||
private fun legacyOnBackHandler() {
|
||||
store.dispatch(OnboardingWalletAction.OnBackPressed)
|
||||
}
|
||||
|
||||
private fun getDeviceScaleFactor(): Float {
|
||||
val typedValue = TypedValue()
|
||||
resources.getValue(R.dimen.device_scale_factor_for_twins_welcome, typedValue, true)
|
||||
return typedValue.float
|
||||
}
|
||||
|
||||
private fun makeSeedPhraseRouter(): SeedPhraseRouter = SeedPhraseRouter(
|
||||
onBack = ::legacyOnBackHandler,
|
||||
onOpenChat = {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
|
||||
val scanResponse = requireNotNull(store.state.globalState.onboardingState.onboardingManager?.scanResponse)
|
||||
|
||||
val getCardInfoUseCase = store.inject(DaggerGraphState::getCardInfoUseCase)
|
||||
val cardInfo = requireNotNull(getCardInfoUseCase.invoke(scanResponse).getOrNull())
|
||||
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::sendFeedbackEmailUseCase)
|
||||
.invoke(type = FeedbackEmailType.DirectUserRequest(cardInfo))
|
||||
}
|
||||
},
|
||||
onOpenUriClick = { uri ->
|
||||
store.dispatchOpenUrl(uri.toString())
|
||||
},
|
||||
)
|
||||
|
||||
private fun makeSeedPhraseMediator(): SeedPhraseMediator {
|
||||
return object : SeedPhraseMediator {
|
||||
override fun createWallet(callback: (CompletionResult<CreateWalletResponse>) -> Unit) {
|
||||
store.dispatch(OnboardingWallet2Action.CreateWallet(callback))
|
||||
}
|
||||
|
||||
override fun onWalletCreated(result: CompletionResult<CreateWalletResponse>) {
|
||||
store.dispatch(OnboardingWallet2Action.WalletWasCreated(result))
|
||||
}
|
||||
|
||||
override fun importWallet(
|
||||
mnemonicComponents: List<String>,
|
||||
passphrase: String?,
|
||||
seedPhraseSource: SeedPhraseSource,
|
||||
callback: (CompletionResult<CreateWalletResponse>) -> Unit,
|
||||
) {
|
||||
store.dispatch(
|
||||
OnboardingWallet2Action.ImportWallet(
|
||||
mnemonicComponents,
|
||||
passphrase,
|
||||
seedPhraseSource,
|
||||
callback,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun allowScreenshots(allow: Boolean) {
|
||||
setSecurityMode(value = !allow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSecurityMode(value: Boolean) {
|
||||
Timber.d("Security mode: ${if (value) "enabled" else "disabled"}")
|
||||
|
||||
if (value) {
|
||||
requireActivity().window.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
WindowManager.LayoutParams.FLAG_SECURE,
|
||||
)
|
||||
} else {
|
||||
requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.animation.PropertyValuesHolder
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.animation.doOnEnd
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapView
|
||||
import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget
|
||||
|
||||
class WalletCardsWidget(
|
||||
val leapfrogWidget: LeapfrogWidget,
|
||||
private val deviceScaleFactor: Float = 1f,
|
||||
) {
|
||||
|
||||
private val animDuration: Long = 400
|
||||
|
||||
private var currentState: WidgetState? = null
|
||||
|
||||
fun toWelcome(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.WELCOME) return
|
||||
|
||||
currentState = WidgetState.WELCOME
|
||||
|
||||
val animator = createAnimator(animate, onEnd = onEnd)
|
||||
animator.playTogether(createAnimators(::createWelcomeProperties))
|
||||
leapfrogWidget.fold { animator.start() }
|
||||
}
|
||||
|
||||
fun toFolded(animate: Boolean = true, duration: Long = animDuration, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.FOLDED) return
|
||||
|
||||
currentState = WidgetState.FOLDED
|
||||
|
||||
val animator = createAnimator(animate, duration) {
|
||||
leapfrogWidget.fold(animate, onEnd)
|
||||
}
|
||||
animator.playTogether(createAnimators(::createLeapfrogProperties))
|
||||
animator.start()
|
||||
}
|
||||
|
||||
fun toFan(animate: Boolean = true, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.FAN) return
|
||||
|
||||
currentState = WidgetState.FAN
|
||||
|
||||
val animator = createAnimator(animate, onEnd = onEnd)
|
||||
animator.playTogether(createAnimators(::createFanProperties))
|
||||
leapfrogWidget.fold { animator.start() }
|
||||
}
|
||||
|
||||
fun toLeapfrog(animate: Boolean = true, onEndFold: () -> Unit = {}, onEnd: () -> Unit = {}) {
|
||||
if (currentState == WidgetState.LEAPFROG) return
|
||||
|
||||
currentState = WidgetState.LEAPFROG
|
||||
|
||||
val onAnimatorEnd = {
|
||||
leapfrogWidget.fold(animate) {
|
||||
leapfrogWidget.initViews()
|
||||
onEndFold()
|
||||
leapfrogWidget.unfold(animate, onEnd)
|
||||
}
|
||||
}
|
||||
val animator = createAnimator(animate, onEnd = onAnimatorEnd)
|
||||
animator.playTogether(createAnimators(::createLeapfrogProperties))
|
||||
animator.start()
|
||||
}
|
||||
|
||||
private fun createAnimators(propertyFactory: (BackupCardType) -> CardProperties): List<ObjectAnimator> {
|
||||
return getCardTypesTypes().map { createCardAnimator(it, propertyFactory(it)) }
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getCardTypesTypes(): List<BackupCardType> = when (leapfrogWidget.getViewsCount()) {
|
||||
2 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP)
|
||||
3 -> listOf(BackupCardType.ORIGIN, BackupCardType.FIRST_BACKUP, BackupCardType.SECOND_BACKUP)
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
private fun createAnimator(animate: Boolean, duration: Long = animDuration, onEnd: () -> Unit): AnimatorSet {
|
||||
return AnimatorSet().apply {
|
||||
this.duration = if (animate) duration else 0
|
||||
doOnEnd { onEnd() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCardAnimator(cardType: BackupCardType, properties: CardProperties): ObjectAnimator {
|
||||
val view = getLeapViewByCard(cardType).view
|
||||
val animator = ObjectAnimator.ofPropertyValuesHolder(
|
||||
view,
|
||||
*properties.createValuesHolders().toTypedArray(),
|
||||
)
|
||||
view.elevation = properties.elevation
|
||||
return animator
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun createWelcomeProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 440f * deviceScaleFactor,
|
||||
yTranslation = 70f,
|
||||
rotation = 75f,
|
||||
elevation = 2f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 10f * deviceScaleFactor,
|
||||
yTranslation = -30f,
|
||||
rotation = 100f,
|
||||
elevation = 1f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = -440f * deviceScaleFactor,
|
||||
yTranslation = -30f,
|
||||
rotation = 70f,
|
||||
elevation = 0f,
|
||||
scale = .85f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun createFanProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 30f,
|
||||
rotation = 5f,
|
||||
elevation = 2f,
|
||||
scale = 1f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = -50f,
|
||||
rotation = -5f,
|
||||
elevation = 1f,
|
||||
scale = 0.9f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = 10f,
|
||||
yTranslation = -120f,
|
||||
rotation = -20f,
|
||||
elevation = 0f,
|
||||
scale = 0.8f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun createLeapfrogProperties(cardType: BackupCardType): CardProperties {
|
||||
return when (cardType) {
|
||||
BackupCardType.ORIGIN -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 2f,
|
||||
scale = 1f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.FIRST_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 1f,
|
||||
scale = 0.9f * deviceScaleFactor,
|
||||
)
|
||||
BackupCardType.SECOND_BACKUP -> CardProperties(
|
||||
xTranslation = 0f,
|
||||
yTranslation = 0f,
|
||||
rotation = 0f,
|
||||
elevation = 0f,
|
||||
scale = 0.8f * deviceScaleFactor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
fun getOriginCardView(): ImageView {
|
||||
return getLeapViewByCard(BackupCardType.ORIGIN).view as ImageView
|
||||
}
|
||||
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
fun getFirstBackupCardView(): ImageView {
|
||||
return getLeapViewByCard(BackupCardType.FIRST_BACKUP).view as ImageView
|
||||
}
|
||||
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
fun getSecondBackupCardView(): ImageView {
|
||||
return getLeapViewByCard(BackupCardType.SECOND_BACKUP).view as ImageView
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
private fun getLeapViewByCard(cardType: BackupCardType): LeapView {
|
||||
fun getByIndex(index: Int): LeapView {
|
||||
return leapfrogWidget.getViewByPosition(leapfrogWidget.getViewPositionByIndex(index))
|
||||
}
|
||||
|
||||
return when (leapfrogWidget.getViewsCount()) {
|
||||
2 -> when (cardType) {
|
||||
BackupCardType.ORIGIN -> getByIndex(1)
|
||||
BackupCardType.FIRST_BACKUP -> getByIndex(0)
|
||||
BackupCardType.SECOND_BACKUP -> throw UnsupportedOperationException()
|
||||
}
|
||||
3 -> when (cardType) {
|
||||
BackupCardType.ORIGIN -> getByIndex(2)
|
||||
BackupCardType.FIRST_BACKUP -> getByIndex(1)
|
||||
BackupCardType.SECOND_BACKUP -> getByIndex(0)
|
||||
}
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
enum class WidgetState { WELCOME, FOLDED, FAN, LEAPFROG }
|
||||
}
|
||||
|
||||
enum class BackupCardType { ORIGIN, FIRST_BACKUP, SECOND_BACKUP }
|
||||
|
||||
private data class CardProperties(
|
||||
val xTranslation: Float = 0f,
|
||||
val yTranslation: Float = 0f,
|
||||
val rotation: Float = 0f,
|
||||
val elevation: Float = 0f,
|
||||
val scale: Float = 1.1f,
|
||||
) {
|
||||
|
||||
fun createValuesHolders(): List<PropertyValuesHolder> {
|
||||
return listOf(
|
||||
PropertyValuesHolder.ofFloat(View.TRANSLATION_X, xTranslation),
|
||||
PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, yTranslation),
|
||||
PropertyValuesHolder.ofFloat(View.ROTATION, rotation),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_X, scale),
|
||||
PropertyValuesHolder.ofFloat(View.SCALE_Y, scale),
|
||||
)
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.tangem.tap.common.extensions.hide
|
||||
import com.tangem.tap.common.extensions.show
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.AccessCodeError
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutBackupAccessCodeBinding
|
||||
|
||||
class AccessCodeDialog(context: Context) : BottomSheetDialog(context) {
|
||||
|
||||
private var binding: LayoutBackupAccessCodeBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = LayoutBackupAccessCodeBinding
|
||||
.inflate(LayoutInflater.from(context))
|
||||
setContentView(binding!!.root)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
|
||||
override fun setOnDismissListener(listener: DialogInterface.OnDismissListener?) {
|
||||
super.setOnDismissListener {
|
||||
listener?.onDismiss(it)
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
||||
fun showInfoScreen() = binding?.run {
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_intro_title)
|
||||
layoutBackupAccessCodeInfo.root.show()
|
||||
layoutBackupAccessCodeSubmit.root.hide()
|
||||
layoutBackupAccessCodeInfo.btnAccessCodeCreate.setOnClickListener {
|
||||
store.dispatch(BackupAction.ShowEnterAccessCodeScreen)
|
||||
}
|
||||
}
|
||||
|
||||
fun showEnterAccessCode() = binding?.run {
|
||||
layoutBackupAccessCodeInfo.root.hide()
|
||||
layoutBackupAccessCodeSubmit.root.show()
|
||||
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_intro_title)
|
||||
|
||||
with(layoutBackupAccessCodeSubmit) {
|
||||
btnAccessCodeSubmit.text = context.getText(R.string.common_continue)
|
||||
btnAccessCodeSubmit.setOnClickListener {
|
||||
store.dispatch(BackupAction.CheckAccessCode(etAccessCode.text.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showReenterAccessCode() = binding?.run {
|
||||
layoutBackupAccessCodeInfo.root.hide()
|
||||
layoutBackupAccessCodeSubmit.root.show()
|
||||
accessCodeTitle.text = context.getText(R.string.onboarding_access_code_repeat_code_title)
|
||||
with(layoutBackupAccessCodeSubmit) {
|
||||
etAccessCode.setText("")
|
||||
btnAccessCodeSubmit.text = context.getText(R.string.common_submit)
|
||||
btnAccessCodeSubmit.setOnClickListener {
|
||||
store.dispatch(BackupAction.SaveAccessCodeConfirmation(etAccessCode.text.toString()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun showError(error: AccessCodeError?) = binding?.layoutBackupAccessCodeSubmit?.run {
|
||||
when (error) {
|
||||
AccessCodeError.CodeTooShort ->
|
||||
tilAccessCode.error = context.getString(R.string.onboarding_access_code_too_short)
|
||||
AccessCodeError.CodesDoNotMatch ->
|
||||
tilAccessCode.error = context.getString(R.string.onboarding_access_codes_doesnt_match)
|
||||
null -> tilAccessCode.error = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object AddMoreBackupCardsDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_warning)
|
||||
setMessage(R.string.onboarding_alert_message_not_max_backup_cards_added)
|
||||
setPositiveButton(R.string.common_continue) { _, _ ->
|
||||
store.dispatch(BackupAction.ShowAccessCodeInfoScreen)
|
||||
}
|
||||
setNegativeButton(R.string.common_cancel) { _, _ ->
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal object AttestationFailedDialog {
|
||||
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_error)
|
||||
setMessage(R.string.issuer_signature_loading_failed)
|
||||
setPositiveButton(R.string.common_ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object BackupInProgressDialog {
|
||||
fun create(context: Context): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_warning)
|
||||
setMessage(R.string.onboarding_backup_exit_warning)
|
||||
setPositiveButton(R.string.warning_button_ok) { _, _ ->
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.tap.common.extensions.dispatchDialogHide
|
||||
import com.tangem.tap.features.onboarding.OnboardingDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object InterruptOnboardingDialog {
|
||||
fun create(context: Context, dialog: OnboardingDialog.InterruptOnboarding): Dialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(context.getString(R.string.onboarding_exit_alert_title))
|
||||
setMessage(context.getString(R.string.onboarding_exit_alert_message))
|
||||
setPositiveButton(R.string.common_ok) { _, _ -> dialog.onOk() }
|
||||
setNegativeButton(R.string.common_cancel) { _, _ -> }
|
||||
setOnDismissListener { store.dispatchDialogHide() }
|
||||
setCancelable(false)
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
object ResetBackupCardDialog {
|
||||
fun create(context: Context, cardId: String): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_attention)
|
||||
setMessage(R.string.onboarding_linking_error_card_with_wallets)
|
||||
setPositiveButton(R.string.common_cancel) { _, _ ->
|
||||
Analytics.send(Onboarding.Backup.ResetCancelEvent)
|
||||
}
|
||||
setNegativeButton(R.string.card_settings_action_sheet_reset) { _, _ ->
|
||||
Analytics.send(Onboarding.Backup.ResetPerformEvent)
|
||||
store.dispatch(BackupAction.ResetBackupCard(cardId))
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
}
|
||||
}.create()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package com.tangem.tap.features.saveWallet.redux
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import org.rekotlin.Action
|
||||
|
||||
internal sealed interface SaveWalletAction : Action {
|
||||
data class ProvideBackupInfo(
|
||||
val scanResponse: ScanResponse,
|
||||
val accessCode: String?,
|
||||
val backupCardsIds: Set<String>?,
|
||||
) : SaveWalletAction
|
||||
|
||||
data object AllowToUseBiometrics : SaveWalletAction {
|
||||
data object Success : SaveWalletAction
|
||||
data class Error(val error: TangemError) : SaveWalletAction
|
||||
}
|
||||
|
||||
data object Dismiss : SaveWalletAction
|
||||
|
||||
data object CloseError : SaveWalletAction
|
||||
data object EnrollBiometrics : SaveWalletAction {
|
||||
data object Enroll : SaveWalletAction
|
||||
data object Cancel : SaveWalletAction
|
||||
}
|
||||
|
||||
data class SaveWalletAfterBackup(
|
||||
val hasBackupError: Boolean,
|
||||
val shouldNavigateToWallet: Boolean,
|
||||
) : SaveWalletAction
|
||||
}
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
package com.tangem.tap.features.saveWallet.redux
|
||||
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SaveWalletMiddleware {
|
||||
|
||||
private val saveWalletJobHolder = JobHolder()
|
||||
|
||||
val middleware: Middleware<AppState> = { _, stateProvider ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
val state = stateProvider()
|
||||
if (action is SaveWalletAction && state != null) {
|
||||
handleAction(action, state.saveWalletState)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAction(action: SaveWalletAction, state: SaveWalletState) {
|
||||
when (action) {
|
||||
is SaveWalletAction.AllowToUseBiometrics -> allowToUseBiometrics(state)
|
||||
is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics()
|
||||
is SaveWalletAction.Dismiss -> dismiss(state)
|
||||
is SaveWalletAction.SaveWalletAfterBackup -> saveWalletAfterBackup(
|
||||
state = state,
|
||||
hasBackupError = action.hasBackupError,
|
||||
shouldNavigateToWallet = action.shouldNavigateToWallet,
|
||||
)
|
||||
is SaveWalletAction.AllowToUseBiometrics.Success,
|
||||
is SaveWalletAction.AllowToUseBiometrics.Error,
|
||||
is SaveWalletAction.ProvideBackupInfo,
|
||||
is SaveWalletAction.CloseError,
|
||||
is SaveWalletAction.EnrollBiometrics,
|
||||
is SaveWalletAction.EnrollBiometrics.Cancel,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveWalletAfterBackup(
|
||||
state: SaveWalletState,
|
||||
hasBackupError: Boolean,
|
||||
shouldNavigateToWallet: Boolean,
|
||||
) {
|
||||
scope.launch {
|
||||
val backupInfo = state.backupInfo ?: error("Backup info is null")
|
||||
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::userWalletBuilderFactory)
|
||||
.create(scanResponse = backupInfo.scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder
|
||||
.backupCardsIds(state.backupInfo.backupCardsIds)
|
||||
.hasBackupError(hasBackupError)
|
||||
.build()
|
||||
.guard {
|
||||
Timber.e("User wallet not created")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
.flatMap {
|
||||
saveAccessCodeIfNeeded(accessCode = backupInfo.accessCode, cardsInWallet = userWallet.cardsInWallet)
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
.doOnSuccess {
|
||||
store.onUserWalletSelected(userWallet)
|
||||
if (!shouldNavigateToWallet) {
|
||||
store.dispatchWithMain(OnboardingWalletAction.WalletSaved(userWallet.walletId))
|
||||
}
|
||||
}
|
||||
.doOnResult { if (shouldNavigateToWallet) navigateToWallet() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun enrollBiometrics() {
|
||||
store.inject(DaggerGraphState::settingsManager).openBiometricSettings()
|
||||
}
|
||||
|
||||
private fun allowToUseBiometrics(state: SaveWalletState) = scope.launch {
|
||||
if (tangemSdkManager.checkNeedEnrollBiometrics()) {
|
||||
store.dispatchWithMain(SaveWalletAction.EnrollBiometrics)
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (state.backupInfo != null) {
|
||||
// TODO: Remove after onboarding refactoring
|
||||
Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.On))
|
||||
} else {
|
||||
Analytics.send(
|
||||
MainScreenAnalyticsEvent.EnableBiometrics(
|
||||
state = com.tangem.core.analytics.models.AnalyticsParam.OnOffState.On,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
* because it will be automatically saved on UserWalletsListManager switch
|
||||
*/
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
|
||||
val error = IllegalStateException("No selected user wallet")
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
store.dispatchWithMain(
|
||||
SaveWalletAction.AllowToUseBiometrics.Error(TangemSdkError.ExceptionError(error)),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
handleSuccessAllowing(selectedUserWallet)
|
||||
}.saveIn(saveWalletJobHolder)
|
||||
|
||||
private suspend fun handleSuccessAllowing(userWallet: UserWallet) {
|
||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
|
||||
|
||||
store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = true)
|
||||
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = userWallet.hasAccessCode,
|
||||
)
|
||||
|
||||
store.dispatchWithMain(SaveWalletAction.AllowToUseBiometrics.Success)
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
}
|
||||
|
||||
private fun dismiss(state: SaveWalletState) {
|
||||
if (state.backupInfo != null) {
|
||||
// TODO: Remove after onboarding refactoring
|
||||
Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.Off))
|
||||
} else {
|
||||
Analytics.send(
|
||||
MainScreenAnalyticsEvent.EnableBiometrics(
|
||||
com.tangem.core.analytics.models.AnalyticsParam.OnOffState.Off,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveAccessCodeIfNeeded(
|
||||
accessCode: String?,
|
||||
cardsInWallet: Set<String>,
|
||||
): CompletionResult<Unit> {
|
||||
return when {
|
||||
accessCode.isNullOrBlank() -> {
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
else -> {
|
||||
tangemSdkManager.saveAccessCode(
|
||||
accessCode = accessCode,
|
||||
cardsIds = cardsInWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToWallet() {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package com.tangem.tap.features.saveWallet.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
internal object SaveWalletReducer {
|
||||
fun reduce(action: Action, state: AppState): SaveWalletState {
|
||||
return if (action is SaveWalletAction) {
|
||||
internalReduce(action, state.saveWalletState)
|
||||
} else {
|
||||
state.saveWalletState
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalReduce(action: SaveWalletAction, state: SaveWalletState): SaveWalletState {
|
||||
return when (action) {
|
||||
is SaveWalletAction.ProvideBackupInfo -> state.copy(
|
||||
backupInfo = SaveWalletState.WalletBackupInfo(
|
||||
scanResponse = action.scanResponse,
|
||||
accessCode = action.accessCode,
|
||||
backupCardsIds = action.backupCardsIds,
|
||||
),
|
||||
)
|
||||
is SaveWalletAction.AllowToUseBiometrics,
|
||||
-> state.copy(isSaveInProgress = true)
|
||||
is SaveWalletAction.AllowToUseBiometrics.Error -> state.copy(
|
||||
error = action.error,
|
||||
isSaveInProgress = false,
|
||||
)
|
||||
is SaveWalletAction.AllowToUseBiometrics.Success -> state.copy(
|
||||
backupInfo = null,
|
||||
isSaveInProgress = false,
|
||||
)
|
||||
is SaveWalletAction.Dismiss -> state.copy(
|
||||
backupInfo = null,
|
||||
)
|
||||
is SaveWalletAction.CloseError -> state.copy(
|
||||
error = null,
|
||||
)
|
||||
is SaveWalletAction.EnrollBiometrics -> state.copy(
|
||||
needEnrollBiometrics = true,
|
||||
)
|
||||
is SaveWalletAction.EnrollBiometrics.Enroll,
|
||||
is SaveWalletAction.EnrollBiometrics.Cancel,
|
||||
-> state.copy(
|
||||
needEnrollBiometrics = false,
|
||||
isSaveInProgress = false,
|
||||
)
|
||||
is SaveWalletAction.SaveWalletAfterBackup,
|
||||
-> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.features.saveWallet.redux
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class SaveWalletState(
|
||||
val backupInfo: WalletBackupInfo? = null,
|
||||
val isSaveInProgress: Boolean = false,
|
||||
val needEnrollBiometrics: Boolean = false,
|
||||
val error: TangemError? = null,
|
||||
) : StateType {
|
||||
data class WalletBackupInfo(
|
||||
val scanResponse: ScanResponse,
|
||||
val accessCode: String?,
|
||||
val backupCardsIds: Set<String>?,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
package com.tangem.tap.features.saveWallet.ui
|
||||
|
||||
import android.content.DialogInterface
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeBottomSheetFragment
|
||||
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
||||
import com.tangem.tap.features.saveWallet.ui.components.EnrollBiometricsDialogContent
|
||||
import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent
|
||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
override val expandedHeightFraction: Float = .98f
|
||||
|
||||
private val viewModel by viewModels<SaveWalletViewModel>()
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
viewModel.cancelOrClose()
|
||||
super.onCancel(dialog)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val enrollBiometricsDialog by rememberUpdatedState(newValue = state.enrollBiometricsDialog)
|
||||
|
||||
Box(modifier = modifier) {
|
||||
SaveWalletScreenContent(
|
||||
showProgress = state.showProgress,
|
||||
onSaveWalletClick = viewModel::saveWallet,
|
||||
onCloseClick = {
|
||||
viewModel.cancelOrClose()
|
||||
dismissAllowingStateLoss()
|
||||
},
|
||||
)
|
||||
|
||||
SnackbarHost(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(vertical = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
}
|
||||
|
||||
EnrollBiometricsDialog(dialog = enrollBiometricsDialog)
|
||||
|
||||
LaunchedEffect(key1 = errorMessage) {
|
||||
errorMessage?.let {
|
||||
snackbarHostState.showSnackbar(it)
|
||||
viewModel.closeError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TopLevelComposableFunctions")
|
||||
@Composable
|
||||
private fun EnrollBiometricsDialog(dialog: EnrollBiometricsDialog?) {
|
||||
if (dialog == null) return
|
||||
EnrollBiometricsDialogContent(dialog)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue