diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b36d773b95..154659cd53 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 21808cc1b9..cac33d689c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,6 +43,7 @@ android:resizeableActivity="@bool/resizeable_activity" android:roundIcon="@mipmap/ic_launcher" android:supportsRtl="true" + android:enableOnBackInvokedCallback="true" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:allowBackup, android:fullBackupContent, android:label"> @@ -127,7 +128,7 @@ diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 29f73d909e..a20134ee4b 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 29f73d909e14cb3760118b197b1929a6088bc40e +Subproject commit a20134ee4be4d7e1e34c6f44089b3cb0713eacc3 diff --git a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt b/app/src/main/java/com/tangem/tap/DecomposeFragment.kt deleted file mode 100644 index 3136ae77dd..0000000000 --- a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt +++ /dev/null @@ -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>( - private val contextProvider: Provider, - 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>() - - fun > newInstance( - tag: String, - contextProvider: Provider, - params: P, - componentFactory: F, - ): Fragment { - this@Companion.componentsBuilders[tag] = ComponentBuilder(contextProvider, params, componentFactory) - - return DecomposeFragment().apply { - this.arguments = bundleOf(TAG_KEY to tag) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 3eb6102ce8..66df0f2d73 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -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() @@ -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(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() { - 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() { diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 4cc52e4409..d61bb4e130 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -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 { diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 533cdac6a9..814384c569 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -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 { 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 { 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 { 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( diff --git a/app/src/main/java/com/tangem/tap/common/TestActions.kt b/app/src/main/java/com/tangem/tap/common/TestActions.kt index 955b89e867..5dc923cf58 100644 --- a/app/src/main/java/com/tangem/tap/common/TestActions.kt +++ b/app/src/main/java/com/tangem/tap/common/TestActions.kt @@ -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 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() - 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) } - } - } -} \ No newline at end of file +typealias TestAction = Pair Unit> \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt deleted file mode 100644 index 243a2f8770..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt +++ /dev/null @@ -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) { - 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() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 919c21972d..c865434607 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -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.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.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.dispatchDebugErrorNotification(error: TapError) { + inject(DaggerGraphState::uiMessageSender).send(SnackbarMessage(stringReference(error.message ?: "debug error"))) } fun Store<*>.dispatchDialogShow(dialog: StateDialog) { diff --git a/app/src/main/java/com/tangem/tap/common/images/Coil.kt b/app/src/main/java/com/tangem/tap/common/images/Coil.kt index 4b269bdca4..28336b233f 100644 --- a/app/src/main/java/com/tangem/tap/common/images/Coil.kt +++ b/app/src/main/java/com/tangem/tap/common/images/Coil.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt index 1188cd7d03..8c9f98ebdd 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt @@ -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, - ) : AppDialog() - data class RemoveWalletDialog( val currencyTitle: String, val onOk: () -> Unit, diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt index 80b48b4193..111bad630c 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppReducer.kt @@ -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), ) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt index 04d6fa4896..3df1fae79b 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt @@ -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> { 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, diff --git a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt deleted file mode 100644 index 1b862e7ed4..0000000000 --- a/app/src/main/java/com/tangem/tap/common/redux/NotificationsMiddleware.kt +++ /dev/null @@ -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? = null) { - baseLayout.get()?.let { - showNotification(getMessageString(it.context, message, args)) - } - } - - fun showToastNotification(message: Int, args: List? = null) { - baseLayout.get()?.let { - Toast.makeText(it.context, getMessageString(it.context, message, args), Toast.LENGTH_LONG).show() - } - } - - fun showNotification(errorList: List?>>, builder: (List) -> 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? = null) { - baseLayout.get()?.let { - showDebugNotification(getMessageString(it.context, message, args)) - } - } -} - -fun getMessageString(context: Context, message: Int, args: List?): String { - return if (args.isNullOrEmpty()) { - context.getString(message) - } else { - context.getString(message, *args.toTypedArray()) - } -} - -val notificationsMiddleware: Middleware = { 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 \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index 1c984ff096..0a43a25887 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index c8372a3d0d..51da092058 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -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) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt index 22932d20e9..1eea5ec1a0 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt b/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt deleted file mode 100644 index 3996e28d5c..0000000000 --- a/app/src/main/java/com/tangem/tap/common/toggleWidget/RefreshBalanceWidget.kt +++ /dev/null @@ -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(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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt deleted file mode 100644 index df54352380..0000000000 --- a/app/src/main/java/com/tangem/tap/common/ui/AddressInfoBottomSheetDialog.kt +++ /dev/null @@ -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, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/data/DefaultVisaAuthTokenStorage.kt b/app/src/main/java/com/tangem/tap/data/DefaultVisaAuthTokenStorage.kt index a6128ddfe6..9859e3e4be 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultVisaAuthTokenStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultVisaAuthTokenStorage.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/data/DefaultVisaOTPStorage.kt b/app/src/main/java/com/tangem/tap/data/DefaultVisaOTPStorage.kt index f752d10dea..b87d3fbfdf 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultVisaOTPStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultVisaOTPStorage.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt b/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt new file mode 100644 index 0000000000..02a9b8be72 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/data/FirebasePushNotificationsTokenProvider.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt index 9870501655..ae906c4a76 100644 --- a/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt +++ b/app/src/main/java/com/tangem/tap/data/RuntimeUserWalletsStore.kt @@ -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" } } diff --git a/app/src/main/java/com/tangem/tap/di/data/PushNotificationsModule.kt b/app/src/main/java/com/tangem/tap/di/data/PushNotificationsModule.kt new file mode 100644 index 0000000000..063efaf3dc --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/data/PushNotificationsModule.kt @@ -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 +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt index f6467cc073..b55ef9a8f7 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/ManageTokensDomainModule.kt @@ -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, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt index 3a568db5e6..7666729a36 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/MarketsDomainModule.kt @@ -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, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/MnemonicModule.kt b/app/src/main/java/com/tangem/tap/di/domain/MnemonicModule.kt deleted file mode 100644 index 8f62a7e255..0000000000 --- a/app/src/main/java/com/tangem/tap/di/domain/MnemonicModule.kt +++ /dev/null @@ -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)) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt index a8cb6b030d..090a030029 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/NFTDomainModule.kt @@ -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, + ) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt new file mode 100644 index 0000000000..fe5ea6d52f --- /dev/null +++ b/app/src/main/java/com/tangem/tap/di/domain/NotificationsDomainModule.kt @@ -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, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index cb6976b9b8..22f61dc1f1 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -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, ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 237b83807e..ff23a2c9e2 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index adc63cbd3d..77c2518104 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletConnectDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletConnectDomainModule.kt index e542ddc5fd..fd5d2491d3 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletConnectDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletConnectDomainModule.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 9be71f685e..4096521b75 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 8225f9014c..63058e7738 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -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)) } } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index 7117819925..547dfbd6ce 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -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) } } diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index 6ea8247faf..29665865af 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -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) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt index b9d6aff5fd..0912058b65 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/chains/CheckForOnboardingChain.kt @@ -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() { 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() diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt index 890ce4ba9b..dd4e607371 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt @@ -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 { + 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 = 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 { 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 { 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 { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt index 18ec47b7fb..55dd835a3e 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -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, ) { + 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, ), ) diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt index 2927924f8c..adc8e80741 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/DefaultTokensFeatureToggles.kt @@ -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") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt index e7a9f1873f..95e75741b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt @@ -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, @Json(name = "scanResponse") diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt index 9ee2498fa8..d963dc5794 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsPublicInformationRepository.kt @@ -20,9 +20,12 @@ internal class DefaultUserWalletsPublicInformationRepository( moshi: Moshi, private val secureStorage: SecureStorage, ) : UserWalletsPublicInformationRepository { - private val publicInformationAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(List::class.java, UserWalletPublicInformation::class.java), - ) + + private val publicInformationAdapter: JsonAdapter> by lazy { + moshi.adapter( + Types.newParameterizedType(List::class.java, UserWalletPublicInformation::class.java), + ) + } override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { return withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt index 13c56f4f22..a1888ea494 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/implementation/DefaultUserWalletsSensitiveInformationRepository.kt @@ -24,12 +24,15 @@ internal class DefaultUserWalletsSensitiveInformationRepository( private val secureStorage: SecureStorage, ) : UserWalletsSensitiveInformationRepository { - private val sensitiveInformationAdapter: JsonAdapter = moshi.adapter( - UserWalletSensitiveInformation::class.java, - ) - private val encryptedSensitiveInformationMapAdapter: JsonAdapter> = moshi.adapter( - Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java), - ) + private val sensitiveInformationAdapter: JsonAdapter by lazy { + moshi.adapter(UserWalletSensitiveInformation::class.java) + } + + private val encryptedSensitiveInformationMapAdapter: JsonAdapter> by lazy { + moshi.adapter( + Types.newParameterizedType(Map::class.java, String::class.java, ByteArray::class.java), + ) + } override suspend fun save(userWallet: UserWallet, encryptionKey: ByteArray?): CompletionResult { if (encryptionKey == null) { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index 4c73101416..8dca07bec0 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt index ad20167f85..b83d23f8d9 100644 --- a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt +++ b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt @@ -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 { @@ -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 } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index aa615456cc..2ac037d2ec 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -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] diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt index 5fe143f6c7..32a138b4eb 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultLegacyWalletConnectRepository.kt @@ -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 by lazy { + if (isNewWc) stub.events else legacy.events + } + override val activeSessions: Flow> by lazy { + if (isNewWc) stub.activeSessions else legacy.activeSessions + } + override val currentSessions: List 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>) { + 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>, blockchainNames: List) { + 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 = emptyFlow() + override val activeSessions: Flow> = emptyFlow() + override val currentSessions: List = listOf() + + override fun init(projectId: String) = Unit + + override fun setUserNamespaces(userNamespaces: Map>) = Unit + + override fun updateSessions() = Unit + + override fun pair(uri: String, source: SourceType) = Unit + + override fun disconnect(topic: String) = Unit + + override fun approve(userNamespaces: Map>, blockchainNames: List) = 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, diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt index 3e47ce122d..8d95e1f664 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/di/WalletConnectInteractorModule.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/WalletConnectFeatureToggles.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/DefaultWalletConnectFeatureToggles.kt similarity index 55% rename from app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/WalletConnectFeatureToggles.kt rename to app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/DefaultWalletConnectFeatureToggles.kt index 48b84481db..03cb9b878d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/WalletConnectFeatureToggles.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/toggles/DefaultWalletConnectFeatureToggles.kt @@ -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") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/BaseFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseFragment.kt deleted file mode 100644 index 6321957ee1..0000000000 --- a/app/src/main/java/com/tangem/tap/features/BaseFragment.kt +++ /dev/null @@ -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(R.id.toolbar)?.setNavigationOnClickListener { - handler.handleOnBackPressed() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt deleted file mode 100644 index e295c9f100..0000000000 --- a/app/src/main/java/com/tangem/tap/features/BaseStoreFragment.kt +++ /dev/null @@ -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>() - - abstract fun subscribeToStore() - - override fun onStart() { - super.onStart() - subscribeToStore() - } - - override fun onStop() { - storeSubscribersList.forEach { store.unsubscribe(it) } - storeSubscribersList.clear() - super.onStop() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index 4895cd65b9..577ac2f3a8 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -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( diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt deleted file mode 100644 index 4b3ac9357d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoOnboardingNoteMiddleware.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt index 894ab1be67..1390b8d0bd 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/cardsettings/model/CardSettingsModel.kt @@ -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() @@ -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 diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt index 5646eee001..98c792c2ef 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/common/DetailsComposeElements.kt @@ -1,6 +1,5 @@ package com.tangem.tap.features.details.ui.common -import androidx.activity.compose.BackHandler import androidx.annotation.StringRes import androidx.compose.foundation.layout.* import androidx.compose.foundation.selection.selectable @@ -29,8 +28,6 @@ internal fun SettingsScreensScaffold( ) { val backgroundColor = TangemTheme.colors.background.secondary - BackHandler(onBack = onBackClick) - Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, topBar = { diff --git a/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt b/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt index a7a853a86a..9f0bff8ab1 100644 --- a/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt +++ b/app/src/main/java/com/tangem/tap/features/home/DefaultHomeComponent.kt @@ -2,14 +2,18 @@ package com.tangem.tap.features.home import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import com.arkivanov.essenty.lifecycle.subscribe import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.components.SystemBarsIconsDisposable +import com.tangem.core.ui.res.LocalRootBackgroundColor import com.tangem.core.ui.utils.findActivity import com.tangem.tap.common.redux.AppState import com.tangem.tap.features.home.api.HomeComponent @@ -61,6 +65,17 @@ internal class DefaultHomeComponent @AssistedInject constructor( onShopButtonClick = model::onShopClick, onSearchTokensClick = model::onSearchClick, ) + + val rootBackgroundColor = LocalRootBackgroundColor.current + val previousColor = remember { rootBackgroundColor.value } + val storiesBackgroundColor = Color(color = 0xFF010101) + + DisposableEffect(Unit) { + rootBackgroundColor.value = storiesBackgroundColor + onDispose { + rootBackgroundColor.value = previousColor + } + } } override fun newState(state: HomeState) { diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index e6a4d37f48..c00ae2518f 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -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)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt index 97f17bd9b5..e2de5c257b 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingDialog.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 7eda3eb6c5..0f3997d317 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -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? = 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? = 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?, - 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)) - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt index e982cd5f2e..8ea87b04a4 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -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? = 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) { - 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, - ) - } -} \ No newline at end of file +class OnboardingManager(var scanResponse: ScanResponse) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt deleted file mode 100644 index c619d4bd17..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingMenuProvider.kt +++ /dev/null @@ -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, -) : 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 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt deleted file mode 100644 index 0922d3f023..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt +++ /dev/null @@ -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 : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber { - - 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() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt deleted file mode 100644 index 8346f444cd..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/OnboardingNoteFragment.kt +++ /dev/null @@ -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() { - - 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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt deleted file mode 100644 index ebdf31becb..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteAction.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt deleted file mode 100644 index 99c704189c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteMiddleware.kt +++ /dev/null @@ -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 = { 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 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt deleted file mode 100644 index 14f7989391..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteReducer.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt deleted file mode 100644 index 3d659e39da..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt +++ /dev/null @@ -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.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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt deleted file mode 100644 index 2e6ac715e0..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ /dev/null @@ -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() { - - 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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt deleted file mode 100644 index 60c3c3927d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsAction.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt deleted file mode 100644 index d5e8144485..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsMiddleware.kt +++ /dev/null @@ -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 = { 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)) - }, - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt deleted file mode 100644 index 3dae886ef6..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt deleted file mode 100644 index f502350050..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt +++ /dev/null @@ -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.values().toList(), -) : StateType { - - val progress: Int - get() = steps.indexOf(currentStep) -} - -enum class OnboardingOtherCardsStep { - None, CreateWallet, Done -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt deleted file mode 100644 index ca00a0c255..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsAction.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt deleted file mode 100644 index 9b0daea01d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ /dev/null @@ -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 = { 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() } - } - } - } - } - } - 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 { - 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 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt deleted file mode 100644 index 594dd4e77c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsReducer.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt deleted file mode 100644 index 83e641bc8a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt +++ /dev/null @@ -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 - 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() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt deleted file mode 100644 index f058c94f3b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/OnboardingTwinsFragment.kt +++ /dev/null @@ -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() { - - 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() ?: "" -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/TwinningProcessNotCompletedDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/TwinningProcessNotCompletedDialog.kt deleted file mode 100644 index 60faf916d7..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/dialog/TwinningProcessNotCompletedDialog.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/BackupDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/BackupDialog.kt new file mode 100644 index 0000000000..ef465fad15 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/BackupDialog.kt @@ -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() +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/BackupMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/BackupMiddleware.kt new file mode 100644 index 0000000000..c3567c8f3a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/BackupMiddleware.kt @@ -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 = { 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, + ), + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index a4be295931..bf97ad0ae2 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -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, - ) : 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) -> Unit, - ) : OnboardingWallet2Action() - - data class ImportWallet( - val mnemonicComponents: List, - val passphrase: String?, - val seedPhraseSource: SeedPhraseSource, - val callback: (CompletionResult) -> Unit, - ) : OnboardingWallet2Action() - - data class WalletWasCreated( - val result: CompletionResult, - ) : 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() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt deleted file mode 100644 index 3fefa3a904..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ /dev/null @@ -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 = { 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 { - 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 = { 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 { - 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) - }, - ), - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt deleted file mode 100644 index d34a661c97..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt deleted file mode 100644 index 8d8f92f601..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ /dev/null @@ -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 = emptyList(), - val backupCardIds: List = emptyList(), - val backupCardBatchIds: List = emptyList(), - @Transient - val backupCardsArtworks: Map = 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() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt deleted file mode 100644 index 0927376b54..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupAnimator.kt +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt deleted file mode 100644 index 6ea3bf1a25..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupInfoAdapter.kt +++ /dev/null @@ -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() { - - 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) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingSeedPhraseStateHandler.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingSeedPhraseStateHandler.kt deleted file mode 100644 index 475a6d64b9..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingSeedPhraseStateHandler.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt deleted file mode 100644 index 99ecf0792a..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ /dev/null @@ -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, - 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() - - 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(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) -> Unit) { - store.dispatch(OnboardingWallet2Action.CreateWallet(callback)) - } - - override fun onWalletCreated(result: CompletionResult) { - store.dispatch(OnboardingWallet2Action.WalletWasCreated(result)) - } - - override fun importWallet( - mnemonicComponents: List, - passphrase: String?, - seedPhraseSource: SeedPhraseSource, - callback: (CompletionResult) -> 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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt deleted file mode 100644 index 4a06365222..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/WalletCardsWidget.kt +++ /dev/null @@ -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 { - return getCardTypesTypes().map { createCardAnimator(it, propertyFactory(it)) } - } - - @Suppress("MagicNumber") - private fun getCardTypesTypes(): List = 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 { - 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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt deleted file mode 100644 index bdeec1a22b..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AccessCodeDialog.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt deleted file mode 100644 index 07a64cc81e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AddMoreBackupCardsDialog.kt +++ /dev/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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt deleted file mode 100644 index 5b6c8b8c85..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/AttestationFailedDialog.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt deleted file mode 100644 index 5094c1f4e9..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/BackupInProgressDialog.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt deleted file mode 100644 index f0249cfcd2..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/InterruptOnboardingDialog.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ResetBackupCardDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ResetBackupCardDialog.kt deleted file mode 100644 index 90458e6f63..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/ResetBackupCardDialog.kt +++ /dev/null @@ -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() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt deleted file mode 100644 index f4981a8c5c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ /dev/null @@ -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?, - ) : 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 -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt deleted file mode 100644 index d8f15fd3d7..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ /dev/null @@ -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 = { _, 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, - ): CompletionResult { - return when { - accessCode.isNullOrBlank() -> { - CompletionResult.Success(Unit) - } - else -> { - tangemSdkManager.saveAccessCode( - accessCode = accessCode, - cardsIds = cardsInWallet, - ) - } - } - } - - private fun navigateToWallet() { - store.dispatchNavigationAction { - replaceAll(AppRoute.Wallet) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt deleted file mode 100644 index 370f7cd935..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletReducer.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt deleted file mode 100644 index b57528ceaa..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletState.kt +++ /dev/null @@ -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?, - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt deleted file mode 100644 index 7062719124..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletBottomSheetFragment.kt +++ /dev/null @@ -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() - - 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) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt deleted file mode 100644 index 51dbdcadc0..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletScreenState.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.tangem.tap.features.saveWallet.ui - -import androidx.compose.runtime.Immutable -import com.tangem.tap.features.details.ui.cardsettings.TextReference -import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog - -@Immutable -internal data class SaveWalletScreenState( - val showProgress: Boolean = false, - val enrollBiometricsDialog: EnrollBiometricsDialog? = null, - val error: TextReference? = null, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt deleted file mode 100644 index 40164976d3..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/SaveWalletViewModel.kt +++ /dev/null @@ -1,92 +0,0 @@ -package com.tangem.tap.features.saveWallet.ui - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.domain.settings.SetSaveWalletScreenShownUseCase -import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.features.details.ui.cardsettings.TextReference -import com.tangem.tap.features.saveWallet.redux.SaveWalletAction -import com.tangem.tap.features.saveWallet.redux.SaveWalletState -import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog -import com.tangem.tap.store -import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import org.rekotlin.StoreSubscriber -import javax.inject.Inject - -@HiltViewModel -internal class SaveWalletViewModel @Inject constructor( - private val analyticsEventHandler: AnalyticsEventHandler, - private val setSaveWalletScreenShownUseCase: SetSaveWalletScreenShownUseCase, - dispatchers: AppCoroutineDispatcherProvider, -) : ViewModel(), StoreSubscriber { - - private val stateInternal = MutableStateFlow(SaveWalletScreenState()) - val state: StateFlow = stateInternal - - init { - subscribeToStoreChanges() - - viewModelScope.launch(dispatchers.main) { - setSaveWalletScreenShownUseCase() - } - } - - fun saveWallet() { - analyticsEventHandler.send(WalletScreenAnalyticsEvent.MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On)) - store.dispatch(SaveWalletAction.AllowToUseBiometrics) - } - - fun cancelOrClose() { - analyticsEventHandler.send( - WalletScreenAnalyticsEvent.MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.Off), - ) - store.dispatch(SaveWalletAction.Dismiss) - } - - fun closeError() { - store.dispatch(SaveWalletAction.CloseError) - } - - override fun newState(state: SaveWalletState) { - stateInternal.update { prevState -> - prevState.copy( - showProgress = state.isSaveInProgress, - enrollBiometricsDialog = if (state.needEnrollBiometrics) createEnrollBiometricsDialog() else null, - error = state.error - ?.takeUnless { it.silent } - ?.let { error -> - error.messageResId?.let { TextReference.Res(it) } - ?: TextReference.Str(error.customMessage) - }, - ) - } - } - - override fun onCleared() { - store.unsubscribe(this) - } - - private fun subscribeToStoreChanges() { - store.subscribe(this) { appState -> - appState.skip { old, new -> old.saveWalletState == new.saveWalletState } - .select { it.saveWalletState } - } - } - - private fun createEnrollBiometricsDialog() = EnrollBiometricsDialog( - onEnroll = { - store.dispatchOnMain(SaveWalletAction.EnrollBiometrics.Enroll) - }, - onCancel = { - store.dispatchOnMain(SaveWalletAction.EnrollBiometrics.Cancel) - }, - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt deleted file mode 100644 index 54da091df6..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/EnrollBiometricsDialogConent.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.tangem.tap.features.saveWallet.ui.components - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.R -import com.tangem.core.ui.components.BasicDialog -import com.tangem.core.ui.components.DialogButtonUM -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog - -@Composable -fun EnrollBiometricsDialogContent(dialog: EnrollBiometricsDialog) { - BasicDialog( - title = stringResourceSafe(R.string.save_user_wallet_agreement_enroll_biometrics_title), - message = stringResourceSafe(R.string.save_user_wallet_agreement_enroll_biometrics_description), - confirmButton = DialogButtonUM( - title = stringResourceSafe(R.string.common_enable), - onClick = dialog.onEnroll, - ), - dismissButton = DialogButtonUM( - onClick = dialog.onCancel, - ), - onDismissDialog = dialog.onCancel, - ) -} - -// region Preview -@Composable -private fun EnrollBiometricDialogContentSample(modifier: Modifier = Modifier) { - Column( - modifier = modifier - .background(TangemTheme.colors.background.primary), - ) { - EnrollBiometricsDialogContent(dialog = EnrollBiometricsDialog({}, {})) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun EnrollBiometricDialogContentPreview() { - TangemThemePreview { - EnrollBiometricDialogContentSample() - } -} -// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt deleted file mode 100644 index 403c914016..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/components/SaveWalletScreenContent.kt +++ /dev/null @@ -1,193 +0,0 @@ -package com.tangem.tap.features.saveWallet.ui.components - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.* -import com.tangem.core.ui.components.atoms.Hand -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.wallet.R - -@Composable -internal fun SaveWalletScreenContent(showProgress: Boolean, onSaveWalletClick: () -> Unit, onCloseClick: () -> Unit) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Header(onCloseClick = onCloseClick) - SpacerHHalf() - Title(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing22)) - SpacerH32() - Description( - modifier = Modifier - .padding( - start = TangemTheme.dimens.spacing34, - end = TangemTheme.dimens.spacing56, - ) - .fillMaxWidth(), - ) - SpacerHHalf() - Footer( - showProgress = showProgress, - onSaveWalletClick = onSaveWalletClick, - ) - SpacerH16() - } -} - -@Composable -private fun Header(onCloseClick: () -> Unit) { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - ) { - Hand() - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - ) { - IconButton( - modifier = Modifier.size(TangemTheme.dimens.size32), - onClick = onCloseClick, - ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_close_24), - tint = TangemTheme.colors.icon.secondary, - contentDescription = stringResourceSafe(id = R.string.common_cancel), - ) - } - SpacerW8() - } - } -} - -@Composable -private fun Title(modifier: Modifier = Modifier) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing32), - ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size56), - painter = painterResource(id = R.drawable.ic_fingerprint_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - ) - Text( - text = stringResourceSafe(id = R.string.save_user_wallet_agreement_header_biometrics), - style = TangemTheme.typography.h2, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - ) - } -} - -@Composable -private fun Description(modifier: Modifier = Modifier) { - Column( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), - horizontalAlignment = Alignment.Start, - ) { - DescriptionItem( - iconPainter = painterResource(id = R.drawable.ic_face_recognition_24), - title = stringResourceSafe(id = R.string.save_user_wallet_agreement_access_title), - description = stringResourceSafe(id = R.string.save_user_wallet_agreement_access_description), - ) - DescriptionItem( - iconPainter = painterResource(id = R.drawable.ic_lock_24), - title = stringResourceSafe(id = R.string.save_user_wallet_agreement_code_title), - description = stringResourceSafe(id = R.string.save_user_wallet_agreement_code_description_biometrics), - ) - } -} - -@Composable -private fun Footer(showProgress: Boolean, onSaveWalletClick: () -> Unit) { - Column( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing16) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), - ) { - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - showProgress = showProgress, - text = stringResourceSafe(id = R.string.save_user_wallet_agreement_allow_biometrics), - onClick = onSaveWalletClick, - ) - Text( - modifier = Modifier.fillMaxWidth(fraction = .7f), - text = stringResourceSafe(R.string.save_user_wallet_agreement_notice), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.Center, - ) - } -} - -@Composable -private fun DescriptionItem(iconPainter: Painter, title: String, description: String) { - Row( - verticalAlignment = Alignment.Top, - horizontalArrangement = Arrangement.SpaceEvenly, - ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size24), - painter = iconPainter, - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - ) - SpacerW24() - Column { - Text( - text = title, - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - ) - SpacerH4() - Text( - text = description, - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.secondary, - ) - } - } -} - -// region Preview -@Composable -private fun SaveWalletScreenContentSample(modifier: Modifier = Modifier) { - Column( - modifier = modifier - .background(TangemTheme.colors.background.primary), - ) { - SaveWalletScreenContent( - showProgress = false, - onSaveWalletClick = { /* no-op */ }, - onCloseClick = { /* no-op */ }, - ) - } -} - -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun SaveWalletScreenContentPreview() { - TangemThemePreview { - SaveWalletScreenContentSample() - } -} -// endregion Preview \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/models/EnrollBiometricsDialog.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/ui/models/EnrollBiometricsDialog.kt deleted file mode 100644 index 7f785a95cf..0000000000 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/ui/models/EnrollBiometricsDialog.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.tap.features.saveWallet.ui.models - -import androidx.compose.runtime.Immutable - -@Immutable -data class EnrollBiometricsDialog( - val onEnroll: () -> Unit, - val onCancel: () -> Unit, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt index 5087ef7019..15e74c5498 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/DefaultExpressAuthProvider.kt @@ -1,68 +1,14 @@ package com.tangem.tap.network.auth -import com.tangem.common.CardIdRangeDec -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys -import com.tangem.datasource.local.preferences.utils.getSyncOrDefault -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.wallets.models.UserWallet import com.tangem.lib.auth.ExpressAuthProvider -import kotlinx.coroutines.runBlocking import java.util.UUID import java.util.concurrent.atomic.AtomicReference -internal class DefaultExpressAuthProvider( - private val userWalletsStore: UserWalletsStore, - private val appPreferencesStore: AppPreferencesStore, -) : ExpressAuthProvider { +internal class DefaultExpressAuthProvider : ExpressAuthProvider { private var uuid = AtomicReference(UUID.randomUUID()) - override fun getUserId(): String { - return userWalletsStore.selectedUserWalletOrNull?.walletId?.stringValue ?: error("No user id provided") - } - override fun getSessionId(): String { return uuid.get().toString() } - - override fun getRefCode(): String { - val selectedUserWallet = userWalletsStore.selectedUserWalletOrNull ?: error("Can not get selected user wallet") - - return when { - isRing(selectedUserWallet) -> "ring" - isChangeNow(selectedUserWallet) -> "ChangeNow" - isPartner(selectedUserWallet) -> "partner" - else -> "" - } - } - - private fun isRing(selectedUserWallet: UserWallet): Boolean { - val addedWalletsWithRings = runBlocking { - appPreferencesStore.getSyncOrDefault( - key = PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY, - default = emptySet(), - ) - } - - return addedWalletsWithRings.contains(selectedUserWallet.walletId.stringValue) - } - - private fun isChangeNow(selectedUserWallet: UserWallet): Boolean { - val changeNowRange = CardIdRangeDec( - start = "AF99001800554008", - end = "AF99001800559994", - ) - val card = selectedUserWallet.scanResponse.card - return card.batchId == BATCH_ID_CHANGENOW || changeNowRange?.contains(card.cardId) == true - } - - private fun isPartner(selectedUserWallet: UserWallet): Boolean { - return selectedUserWallet.scanResponse.card.batchId == BATCH_ID_PARTNER - } - - private companion object { - const val BATCH_ID_CHANGENOW = "BB000013" - const val BATCH_ID_PARTNER = "AF990015" - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt index c72a9c145c..5f724624af 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt @@ -2,8 +2,6 @@ package com.tangem.tap.network.auth.di import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage -import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider @@ -30,14 +28,8 @@ internal class AuthModule { @Provides @Singleton - fun provideExpressAuthProvider( - userWalletsStore: UserWalletsStore, - appPreferencesStore: AppPreferencesStore, - ): ExpressAuthProvider { - return DefaultExpressAuthProvider( - userWalletsStore = userWalletsStore, - appPreferencesStore = appPreferencesStore, - ) + fun provideExpressAuthProvider(): ExpressAuthProvider { + return DefaultExpressAuthProvider() } @Provides diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt deleted file mode 100644 index 621ce6f4e9..0000000000 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ /dev/null @@ -1,366 +0,0 @@ -package com.tangem.tap.proxy - -import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager -import com.tangem.blockchain.blockchains.optimism.EthereumOptimisticRollupWalletManager -import com.tangem.blockchain.common.* -import com.tangem.blockchain.common.transaction.Fee -import com.tangem.blockchain.common.transaction.TransactionFee -import com.tangem.blockchain.extensions.Result -import com.tangem.blockchain.externallinkprovider.TxExploreState -import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.lib.crypto.TransactionManager -import com.tangem.lib.crypto.models.* -import java.math.BigDecimal -import java.math.BigInteger -import java.math.MathContext -import java.math.RoundingMode - -@Suppress("LargeClass") -class TransactionManagerImpl( - private val walletManagersFacade: WalletManagersFacade, - private val userWalletsListManager: UserWalletsListManager, -) : TransactionManager { - - override fun getExplorerTransactionLink(networkId: String, txAddress: String): String { - val blockchain = Blockchain.fromNetworkId(networkId) ?: error("blockchain not found") - return when (val txUrlState = blockchain.getExploreTxUrl(txAddress)) { - TxExploreState.Unsupported -> "" - is TxExploreState.Url -> txUrlState.url - } - } - - override suspend fun updateWalletManager(networkId: String, derivationPath: String?) { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - getActualWalletManager(blockchain, derivationPath).update() - } - - @Throws(IllegalStateException::class) - override suspend fun getFee( - networkId: String, - amountToSend: Amount, - currencyToSend: Currency, - destinationAddress: String, - increaseBy: Int?, - data: String?, - derivationPath: String?, - ): ProxyFees { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - val walletManager = getActualWalletManager(blockchain, derivationPath) - if (walletManager is EthereumWalletManager) { - if (walletManager is EthereumOptimisticRollupWalletManager) { - return getFeeForOptimismBlockchain( - walletManager = walletManager, - amount = amountToSend, - destinationAddress = destinationAddress, - data = data, - ) - } - return getFeeForEthereumBlockchain( - walletManager = walletManager, - blockchain = blockchain, - amountToSend = amountToSend, - destinationAddress = destinationAddress, - data = data, - increaseBy = increaseBy, - ) - } else { - return getFeeForBlockchain( - walletManager = walletManager, - amountToSend = amountToSend, - destinationAddress = destinationAddress, - ) - } - } - - override fun getBlockchainInfo(networkId: String): ProxyNetworkInfo { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - return ProxyNetworkInfo( - name = blockchain.fullName, - blockchainId = blockchain.id, - ) - } - - private suspend fun getFeeForBlockchain( - walletManager: WalletManager, - amountToSend: Amount, - destinationAddress: String, - ): ProxyFees { - val fee = (walletManager as? TransactionSender)?.getFee( - amount = amountToSend, - destination = destinationAddress, - ) ?: error("Cannot cast to TransactionSender") - return when (fee) { - is Result.Success -> { - // for not EVM blockchains set gasLimit ZERO for now - when (fee.data) { - is TransactionFee.Single -> { - val singleFee = when (val normalFee = (fee.data as TransactionFee.Single).normal) { - is Fee.CardanoToken -> { - ProxyFee.CardanoToken( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = normalFee.amount), - minAdaValue = normalFee.minAdaValue, - ) - } - is Fee.Filecoin -> { - ProxyFee.Filecoin( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = normalFee.amount), - gasPremium = normalFee.gasPremium, - ) - } - is Fee.Sui -> { - ProxyFee.Sui( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = normalFee.amount), - gasPrice = normalFee.gasPrice, - gasBudget = normalFee.gasBudget, - ) - } - else -> { - ProxyFee.Common( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = normalFee.amount), - ) - } - } - - ProxyFees.SingleFee(singleFee = singleFee) - } - is TransactionFee.Choosable -> { - val choosableFee = fee.data as TransactionFee.Choosable - ProxyFees.MultipleFees( - minFee = ProxyFee.Common( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = choosableFee.minimum.amount), - ), - normalFee = ProxyFee.Common( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = choosableFee.normal.amount), - ), - priorityFee = ProxyFee.Common( - gasLimit = BigInteger.ZERO, - fee = convertToProxyAmount(amount = choosableFee.priority.amount), - ), - ) - } - } - } - is Result.Failure -> { - error(fee.error.message ?: fee.error.customMessage) - } - } - } - - @Suppress("LongParameterList") - private suspend fun getFeeForEthereumBlockchain( - walletManager: EthereumWalletManager, - blockchain: Blockchain, - amountToSend: Amount, - destinationAddress: String, - data: String?, - increaseBy: Int?, - ): ProxyFees { - val gasLimit = getGasLimit( - evmWalletManager = walletManager, - amount = amountToSend, - destinationAddress = destinationAddress, - data = data, - ).increaseBigIntegerByPercents(increaseBy) - return when (val gasPrice = walletManager.getGasPrice()) { - is Result.Success -> { - createMultipleProxyFees(gasPrice = gasPrice.data, gasLimit = gasLimit, blockchain = blockchain) - } - is Result.Failure -> { - error(gasPrice.error.message ?: gasPrice.error.customMessage) - } - } - } - - private suspend fun getFeeForOptimismBlockchain( - walletManager: EthereumOptimisticRollupWalletManager, - amount: Amount, - destinationAddress: String, - data: String?, - ): ProxyFees { - val fee = if (data.isNullOrEmpty()) { - walletManager.getFee(amount, destinationAddress) - } else { - walletManager.getFee(amount, destinationAddress, data) - } - return when (fee) { - is Result.Success -> { - val choosableFee = fee.data - - val minProxyFee = ProxyFee.Common( - gasLimit = (choosableFee.minimum as Fee.Ethereum).gasLimit, - fee = convertToProxyAmount(amount = choosableFee.minimum.amount), - ) - val normalProxyFee = ProxyFee.Common( - gasLimit = (choosableFee.normal as Fee.Ethereum).gasLimit, - fee = convertToProxyAmount(amount = choosableFee.normal.amount), - ) - val priorityProxyFee = ProxyFee.Common( - gasLimit = (choosableFee.priority as Fee.Ethereum).gasLimit, - fee = convertToProxyAmount(amount = choosableFee.priority.amount), - ) - - ProxyFees.MultipleFees( - minFee = minProxyFee, - normalFee = normalProxyFee, - priorityFee = priorityProxyFee, - ) - } - is Result.Failure -> { - error(fee.error.message ?: fee.error.customMessage) - } - } - } - - private suspend fun getGasLimit( - evmWalletManager: EthereumWalletManager, - amount: Amount, - destinationAddress: String, - data: String?, - ): BigInteger { - val result = if (data.isNullOrEmpty()) { - evmWalletManager.getGasLimit( - amount = amount, - destination = destinationAddress, - ) - } else { - evmWalletManager.getGasLimit( - amount = amount, - destination = destinationAddress, - data = data, - ) - } - when (result) { - is Result.Success -> { - return result.data - } - is Result.Failure -> { - error(result.error.message ?: result.error.customMessage) - } - } - } - - override suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - val walletManager = getActualWalletManager(blockchain, derivationPath) - val gasPriceResult = (walletManager as? EthereumWalletManager)?.getGasPrice() - ?: error("not supported for $blockchain") - val gasPrice = when (gasPriceResult) { - is Result.Failure -> error("fail to receive gasPrice") - is Result.Success -> gasPriceResult.data - } - return createMultipleProxyFees(gasPrice, gas, blockchain) - } - - private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager { - val selectedUserWallet = requireNotNull( - userWalletsListManager.selectedUserWalletSync, - ) { "userWallet or userWalletsListManager is null" } - val walletManager = walletManagersFacade.getOrCreateWalletManager( - selectedUserWallet.walletId, - blockchain, - derivationPath, - ) - - return requireNotNull(walletManager) { "no wallet manager found" } - } - - /** - * Create proxy fees - * - * @param gasPrice min fee gasPrice - * @param gasLimit - * @param blockchain - */ - private fun createMultipleProxyFees(gasPrice: BigInteger, gasLimit: BigInteger, blockchain: Blockchain): ProxyFees { - val patchedGasLimit = gasLimit.toBigDecimal().increaseForMantleIfNeeded(blockchain).toBigInteger() - val gasPriceNormal = gasPrice - .increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE) - val gasPricePriority = gasPrice.increaseBigIntegerByPercents(MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE) - val feeMin = patchedGasLimit.multiply(gasPrice).toBigDecimal( - scale = blockchain.decimals(), - mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN), - ).increaseForMantleIfNeeded(blockchain) - val feeNormal = patchedGasLimit.multiply(gasPriceNormal).toBigDecimal( - scale = blockchain.decimals(), - mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN), - ).increaseForMantleIfNeeded(blockchain) - val feePriority = patchedGasLimit.multiply(gasPricePriority).toBigDecimal( - scale = blockchain.decimals(), - mathContext = MathContext(blockchain.decimals(), RoundingMode.HALF_EVEN), - ).increaseForMantleIfNeeded(blockchain) - val minFee = ProxyFee.Common( - gasLimit = patchedGasLimit, - fee = ProxyAmount( - currencySymbol = blockchain.currency, - value = feeMin, - decimals = blockchain.decimals(), - ), - ) - val normalFee = ProxyFee.Common( - gasLimit = patchedGasLimit, - fee = ProxyAmount( - currencySymbol = blockchain.currency, - value = feeNormal, - decimals = blockchain.decimals(), - ), - ) - val priorityFee = ProxyFee.Common( - gasLimit = patchedGasLimit, - fee = ProxyAmount( - currencySymbol = blockchain.currency, - value = feePriority, - decimals = blockchain.decimals(), - ), - ) - return ProxyFees.MultipleFees( - minFee = minFee, - normalFee = normalFee, - priorityFee = priorityFee, - ) - } - - private fun convertToProxyAmount(amount: Amount): ProxyAmount { - return ProxyAmount( - currencySymbol = amount.currencySymbol, - value = amount.value ?: BigDecimal.ZERO, - decimals = amount.decimals, - ) - } - - /** - * Increase big integer by percents - * - * @param percents in format 150 -> 50% - * @return increased value - */ - private fun BigInteger.increaseBigIntegerByPercents(percents: Int?): BigInteger { - return if (percents != null && percents != 0) { - this.multiply(percents.toBigInteger()).divide(BigInteger("100")) - } else { - this - } - } - - // TODO Workaround for Mantle. Remove after [REDACTED_JIRA] - private fun BigDecimal.increaseForMantleIfNeeded(blockchain: Blockchain): BigDecimal { - return if (blockchain == Blockchain.Mantle) { - this.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER) - } else { - this - } - } - - companion object { - private const val MULTIPLIER_GAS_PRICE_FOR_NORMAL_FEE = 150 // 50% - private const val MULTIPLIER_GAS_PRICE_FOR_PRIORITY_FEE = 200 // 50% - private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.8") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index e9bcad2a6b..4d59c1f56e 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -2,10 +2,8 @@ package com.tangem.tap.proxy.di import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.proxy.TransactionManagerImpl import com.tangem.tap.proxy.UserWalletManagerImpl import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -37,16 +35,4 @@ internal object ProxyModule { dispatchers = dispatchers, ) } - - @Provides - @Singleton - fun provideTransactionManager( - walletManagersFacade: WalletManagersFacade, - userWalletsListManager: UserWalletsListManager, - ): TransactionManager { - return TransactionManagerImpl( - walletManagersFacade = walletManagersFacade, - userWalletsListManager = userWalletsListManager, - ) - } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 2525bdd3cd..9d8ccb0fbb 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -32,6 +32,7 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.features.onramp.OnrampFeatureToggles +import com.tangem.operations.attestation.CardArtworksProvider import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository @@ -76,5 +77,6 @@ data class DaggerGraphState( val settingsManager: SettingsManager? = null, val uiMessageSender: UiMessageSender? = null, val onlineCardVerifier: OnlineCardVerifier? = null, + val cardArworksProvider: CardArtworksProvider? = null, val userWalletBuilderFactory: UserWalletBuilder.Factory? = null, ) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt index bcebe2fe83..19f2b9b8a7 100644 --- a/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt +++ b/app/src/main/java/com/tangem/tap/routing/ProxyAppRouter.kt @@ -42,7 +42,11 @@ internal class ProxyAppRouter( override fun replaceAll(vararg routes: AppRoute, onComplete: (isSuccess: Boolean) -> Unit) { safeNavigate(onComplete, message = "Replace all routes with $routes") { - innerRouter.replaceAll(*routes, onComplete = onComplete) + runCatching { + innerRouter.replaceAll(*routes, onComplete = onComplete) + }.getOrElse { + Timber.tag("ASDASD").e(it) + } } } @@ -78,6 +82,7 @@ internal class ProxyAppRouter( override fun defaultCompletionHandler(isSuccess: Boolean, errorMessage: String) { if (!isSuccess) { + Timber.tag("ASDASD").d(errorMessage) analyticsExceptionHandler.sendException(ExceptionAnalyticsEvent(RuntimeException(errorMessage))) Timber.w(errorMessage) diff --git a/app/src/main/java/com/tangem/tap/routing/RootContent.kt b/app/src/main/java/com/tangem/tap/routing/RootContent.kt index d0c69a7abe..b94e0d9aab 100644 --- a/app/src/main/java/com/tangem/tap/routing/RootContent.kt +++ b/app/src/main/java/com/tangem/tap/routing/RootContent.kt @@ -1,6 +1,7 @@ package com.tangem.tap.routing import android.app.Activity +import android.os.Build import android.os.Bundle import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -8,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -17,23 +17,31 @@ import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity import com.arkivanov.decompose.ExperimentalDecomposeApi import com.arkivanov.decompose.extensions.compose.stack.Children -import com.arkivanov.decompose.extensions.compose.stack.animation.* +import com.arkivanov.decompose.extensions.compose.stack.animation.StackAnimation +import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.androidPredictiveBackAnimatable +import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.predictiveBackAnimation +import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.value.Value +import com.arkivanov.essenty.backhandler.BackHandler import com.tangem.common.routing.AppRoute import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.components.snackbar.TangemSnackbarHost import com.tangem.core.ui.message.EventMessageEffect -import com.tangem.core.ui.res.LocalIsNavigationRefactoringEnabled +import com.tangem.core.ui.res.LocalRootBackgroundColor import com.tangem.core.ui.res.LocalSnackbarHostState import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.routing.component.RoutingComponent +import com.tangem.tap.routing.transitions.RoutingTransitionAnimationFactory @OptIn(ExperimentalDecomposeApi::class) @Composable internal fun RootContent( stack: Value>, + backHandler: BackHandler, uiDependencies: UiDependencies, + wcContent: @Composable (modifier: Modifier) -> Unit, + onBack: () -> Unit, modifier: Modifier = Modifier, ) { val context = LocalContext.current @@ -42,43 +50,62 @@ internal fun RootContent( activity = context as Activity, uiDependencies = uiDependencies, ) { - CompositionLocalProvider( - LocalIsNavigationRefactoringEnabled provides true, - ) { - val snackbarHostState = LocalSnackbarHostState.current + val snackbarHostState = LocalSnackbarHostState.current - Box(Modifier.background(TangemTheme.colors.background.primary)) { - Children( - modifier = modifier, - animation = stackAnimation(fade()), - stack = stack, - ) { child -> - when (val instance = child.instance) { - is RoutingComponent.Child.Initial -> Unit - is RoutingComponent.Child.ComposableComponent -> { - instance.component.Content(Modifier.fillMaxSize()) + Box(Modifier.background(LocalRootBackgroundColor.current.value)) { + Children( + modifier = modifier, + animation = childrenAnimation(backHandler = backHandler, onBack = onBack), + stack = stack, + ) { child -> + when (val instance = child.instance) { + is RoutingComponent.Child.Initial -> Unit + is RoutingComponent.Child.ComposableComponent -> { + instance.component.Content(Modifier.fillMaxSize()) + } + is RoutingComponent.Child.LegacyIntent -> { + // TODO: Remove and use it's own router: [REDACTED_JIRA] + LaunchedEffect(instance) { + startActivity(context, instance.intent, Bundle.EMPTY) } - is RoutingComponent.Child.LegacyIntent -> { - // TODO: Remove and use it's own router: [REDACTED_JIRA] - LaunchedEffect(instance) { - startActivity(context, instance.intent, Bundle.EMPTY) - } - } - is RoutingComponent.Child.LegacyFragment, - -> error("Unsupported child: $instance") } } - - TangemSnackbarHost( - modifier = Modifier - .align(Alignment.BottomCenter) - .navigationBarsPadding() - .padding(all = 16.dp), - hostState = snackbarHostState, - ) } - } - EventMessageEffect() + wcContent(Modifier.fillMaxSize()) + + TangemSnackbarHost( + modifier = Modifier + .align(Alignment.BottomCenter) + .navigationBarsPadding() + .padding(all = 16.dp), + hostState = snackbarHostState, + ) + } + } + + EventMessageEffect() +} + +@OptIn(ExperimentalDecomposeApi::class) +private fun childrenAnimation( + backHandler: BackHandler, + onBack: () -> Unit, +): StackAnimation { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + predictiveBackAnimation( + backHandler = backHandler, + onBack = onBack, + selector = { backEvent, _, _ -> + androidPredictiveBackAnimatable(backEvent) + }, + fallbackAnimation = stackAnimation { + RoutingTransitionAnimationFactory.create(it.configuration) + }, + ) + } else { + stackAnimation { + RoutingTransitionAnimationFactory.create(it.configuration) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt index 9709e18553..9fdef856a9 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/RoutingComponent.kt @@ -2,34 +2,18 @@ package com.tangem.tap.routing.component import android.content.Intent import androidx.compose.runtime.Immutable -import androidx.fragment.app.Fragment -import com.arkivanov.decompose.router.stack.ChildStack -import com.arkivanov.decompose.value.Value import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.utils.Provider internal interface RoutingComponent : ComposableContentComponent { - // TODO: Remove after full navigation refactoring - val router: Router - - // TODO: Remove after full navigation refactoring - val stack: Value> - @Immutable sealed class Child { // TODO: Remove and find initial intent in RoutingComponent: [REDACTED_JIRA] data object Initial : Child() - data class LegacyFragment( - val name: String, - val fragmentProvider: Provider, - ) : Child() - data class LegacyIntent(val intent: Intent) : Child() data class ComposableComponent( diff --git a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt index 759951e25e..92ac286e88 100644 --- a/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt +++ b/app/src/main/java/com/tangem/tap/routing/component/impl/DefaultRoutingComponent.kt @@ -1,14 +1,11 @@ package com.tangem.tap.routing.component.impl import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.value.Value import com.arkivanov.decompose.value.subscribe -import com.arkivanov.essenty.lifecycle.doOnDestroy import com.google.android.material.snackbar.Snackbar import com.tangem.common.routing.AppRoute import com.tangem.core.decompose.context.AppComponentContext @@ -18,12 +15,12 @@ import com.tangem.core.ui.UiDependencies import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.features.walletconnect.components.WcRoutingComponent import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.routing.RootContent import com.tangem.tap.routing.component.RoutingComponent import com.tangem.tap.routing.component.RoutingComponent.Child import com.tangem.tap.routing.configurator.AppRouterConfig -import com.tangem.tap.routing.toggle.RoutingFeatureToggles import com.tangem.tap.routing.utils.ChildFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -36,35 +33,36 @@ internal class DefaultRoutingComponent @AssistedInject constructor( private val childFactory: ChildFactory, private val appRouterConfig: AppRouterConfig, private val uiDependencies: UiDependencies, - routingFeatureToggles: RoutingFeatureToggles, + private val wcRoutingComponentFactory: WcRoutingComponent.Factory, ) : RoutingComponent, AppComponentContext by context, SnackbarHandler { - override val stack: Value> = childStack( + private val wcRoutingComponent: WcRoutingComponent by lazy { + wcRoutingComponentFactory + .create(childByContext(componentContext = this), params = Unit) + } + + private val stack: Value> = childStack( source = navigationProvider.getOrCreateTyped(), initialStack = { getInitialStackOrInit() }, serializer = null, // AppRoute.serializer(), // Disabled until Nav refactoring completes - handleBackButton = routingFeatureToggles.isNavigationRefactoringEnabled, - childFactory = ::child, + handleBackButton = true, + childFactory = { route, childContext -> + childFactory.createChild(route, childByContext(childContext)) + }, ) init { - lifecycle.doOnDestroy { - childFactory.doOnDestroy() - } + appRouterConfig.routerScope = componentScope + appRouterConfig.componentRouter = router + appRouterConfig.snackbarHandler = this - if (routingFeatureToggles.isNavigationRefactoringEnabled) { - appRouterConfig.routerScope = componentScope - appRouterConfig.componentRouter = router - appRouterConfig.snackbarHandler = this - - stack.subscribe(lifecycle) { stack -> - val stackItems = stack.items.map { it.configuration } - - if (appRouterConfig.stack != stackItems) { - appRouterConfig.stack = stackItems - } + stack.subscribe(lifecycle) { stack -> + val stackItems = stack.items.map { it.configuration } + wcRoutingComponent.onAppRouteChange(stack.active.configuration) + if (appRouterConfig.stack != stackItems) { + appRouterConfig.stack = stackItems } } } @@ -75,6 +73,9 @@ internal class DefaultRoutingComponent @AssistedInject constructor( modifier = modifier, stack = stack, uiDependencies = uiDependencies, + wcContent = { wcRoutingComponent.Content(it) }, + backHandler = backHandler, + onBack = router::pop, ) } @@ -114,10 +115,6 @@ internal class DefaultRoutingComponent @AssistedInject constructor( initialStack } - private fun child(route: AppRoute, context: ComponentContext): Child { - return childFactory.createChild(route) { childByContext(context) } - } - @AssistedFactory interface Factory : RoutingComponent.Factory { override fun create(context: AppComponentContext, initialStack: List?): DefaultRoutingComponent diff --git a/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt b/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt deleted file mode 100644 index 6554684732..0000000000 --- a/app/src/main/java/com/tangem/tap/routing/toggle/RoutingFeatureToggles.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.tap.routing.toggle - -import com.tangem.core.configtoggle.feature.FeatureTogglesManager -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -internal class RoutingFeatureToggles @Inject constructor( - private val featureTogglesManager: FeatureTogglesManager, -) { - - val isNavigationRefactoringEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled(name = "NAVIGATION_REFACTORING") -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/transitions/RoutingTransitionAnimationFactory.kt b/app/src/main/java/com/tangem/tap/routing/transitions/RoutingTransitionAnimationFactory.kt new file mode 100644 index 0000000000..b1332ff1c8 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/routing/transitions/RoutingTransitionAnimationFactory.kt @@ -0,0 +1,92 @@ +package com.tangem.tap.routing.transitions + +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.animation.core.FiniteAnimationSpec +import androidx.compose.animation.core.tween +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.layout +import com.arkivanov.decompose.extensions.compose.stack.animation.* +import com.tangem.common.routing.AppRoute +import kotlin.compareTo +import kotlin.times + +object RoutingTransitionAnimationFactory { + + @Suppress("MagicNumber") + fun create(appRoute: AppRoute): StackAnimator { + return when (appRoute) { + is AppRoute.Onboarding, + is AppRoute.Welcome, + is AppRoute.Home, + -> fade(tween(400)).plus(scale(tween(400))) + is AppRoute.Wallet, + -> slideAndFade(directions = setOf(Direction.ENTER_BACK, Direction.EXIT_BACK)) + .plus( + scaleWithDirection( + directions = setOf(Direction.ENTER_FRONT, Direction.EXIT_FRONT), + animationSpec = tween(400), + ), + ) + else -> slideAndFade() + } + } + + private fun scaleWithDirection( + directions: Set, + animationSpec: FiniteAnimationSpec = tween(), + frontFactor: Float = 1.15F, + backFactor: Float = 0.95F, + ): StackAnimator = stackAnimator(animationSpec = animationSpec) { factor, direction, content -> + content( + Modifier.graphicsLayer { + val scaleFactor = if (directions.contains(direction)) { + if (factor >= 0F) { + factor * (frontFactor - 1F) + 1F + } else { + factor * (1F - backFactor) + 1F + } + } else { + 1f + } + scaleX = scaleFactor + scaleY = scaleFactor + }, + ) + } + + @Suppress("MagicNumber") + private fun slideAndFade(directions: Set? = null): StackAnimator { + val easing = CubicBezierEasing(0.55f, 0.0f, 0.0f, 1f) + + return stackAnimator( + animationSpec = tween(durationMillis = 400, easing = easing), + ) { factor, direction, content -> + content( + if (directions == null || directions.contains(direction)) { + Modifier.offsetXFactor(factor) + } else { + Modifier + }, + ) + }.plus( + fade( + animationSpec = tween( + delayMillis = 50, + durationMillis = 300, + easing = easing, + ), + ), + ) + } + + @Suppress("MagicNumber") + private fun Modifier.offsetXFactor(factor: Float): Modifier = layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + + layout(placeable.width, placeable.height) { + placeable.placeRelative(x = (placeable.width.toFloat() * factor * 0.15f).toInt(), y = 0) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 3c0ac636bb..0f2b7d5303 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -12,20 +12,19 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent -import com.tangem.features.nft.component.NFTCollectionsComponent -import com.tangem.features.nft.component.NFTDetailsComponent -import com.tangem.features.nft.component.NFTReceiveComponent +import com.tangem.features.nft.component.NFTComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent import com.tangem.features.onramp.component.* import com.tangem.features.pushnotifications.api.PushNotificationsComponent import com.tangem.features.send.api.SendComponent +import com.tangem.features.send.v2.api.NFTSendComponent import com.tangem.features.send.v2.api.SendFeatureToggles import com.tangem.features.staking.api.StakingComponent import com.tangem.features.swap.SwapComponent import com.tangem.features.tester.api.TesterRouter import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.wallet.WalletEntryComponent -import com.tangem.tap.domain.walletconnect2.toggles.WalletConnectFeatureToggles +import com.tangem.features.walletconnect.components.WalletConnectFeatureToggles import com.tangem.tap.features.details.ui.appcurrency.api.AppCurrencySelectorComponent import com.tangem.tap.features.details.ui.appsettings.api.AppSettingsComponent import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent @@ -34,19 +33,11 @@ import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent import com.tangem.tap.features.details.ui.walletconnect.api.WalletConnectComponent import com.tangem.tap.features.home.api.HomeComponent -import com.tangem.tap.features.onboarding.products.note.OnboardingNoteFragment -import com.tangem.tap.features.onboarding.products.otherCards.OnboardingOtherCardsFragment -import com.tangem.tap.features.onboarding.products.twins.ui.OnboardingTwinsFragment -import com.tangem.tap.features.onboarding.products.wallet.ui.OnboardingWalletFragment -import com.tangem.tap.features.saveWallet.ui.SaveWalletBottomSheetFragment import com.tangem.tap.features.welcome.component.WelcomeComponent import com.tangem.tap.routing.component.RoutingComponent.Child -import com.tangem.tap.routing.toggle.RoutingFeatureToggles -import com.tangem.utils.Provider import dagger.hilt.android.scopes.ActivityScoped -import java.util.WeakHashMap import javax.inject.Inject -import com.tangem.features.walletconnect.components.WalletConnectEntryComponent as RedisegnedWalletConnectComponent +import com.tangem.features.walletconnect.components.WalletConnectEntryComponent as RedesignedWalletConnectComponent @ActivityScoped @Suppress("LongParameterList", "LargeClass") @@ -82,25 +73,15 @@ internal class ChildFactory @Inject constructor( private val walletComponentFactory: WalletEntryComponent.Factory, private val sendComponentFactoryV2: com.tangem.features.send.v2.api.SendComponent.Factory, private val sendFeatureToggles: SendFeatureToggles, - private val redesignedWalletConnectComponentFactory: RedisegnedWalletConnectComponent.Factory, - private val nftCollectionsComponentFactory: NFTCollectionsComponent.Factory, - private val nftReceiveComponentFactory: NFTReceiveComponent.Factory, - private val nftDetailsComponentFactory: NFTDetailsComponent.Factory, + private val redesignedWalletConnectComponentFactory: RedesignedWalletConnectComponent.Factory, + private val nftComponentFactory: NFTComponent.Factory, + private val nftSendComponentFactory: NFTSendComponent.Factory, private val testerRouter: TesterRouter, - private val routingFeatureToggles: RoutingFeatureToggles, private val walletConnectFeatureToggles: WalletConnectFeatureToggles, ) { - fun createChild(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child { - return if (routingFeatureToggles.isNavigationRefactoringEnabled) { - createChildNew(route, contextFactory(route)) - } else { - createChildLegacy(route, contextFactory) - } - } - @Suppress("LongMethod", "CyclomaticComplexMethod") - private fun createChildNew(route: AppRoute, context: AppComponentContext): Child { + fun createChild(route: AppRoute, context: AppComponentContext): Child { return when (route) { is AppRoute.Initial -> { Child.Initial @@ -182,7 +163,7 @@ internal class ChildFactory @Inject constructor( is AppRoute.OnrampSuccess -> { createComponentChild( context = context, - params = OnrampSuccessComponent.Params(route.externalTxId), + params = OnrampSuccessComponent.Params(route.txId), componentFactory = onrampSuccessComponentFactory, ) } @@ -405,393 +386,26 @@ internal class ChildFactory @Inject constructor( componentFactory = walletComponentFactory, ) } - is AppRoute.NFTCollections -> + is AppRoute.NFT -> createComponentChild( context = context, - params = NFTCollectionsComponent.Params(userWalletId = route.userWalletId), - componentFactory = nftCollectionsComponentFactory, + params = NFTComponent.Params( + userWalletId = route.userWalletId, + walletName = route.walletName, + ), + componentFactory = nftComponentFactory, ) - is AppRoute.NFTReceive -> + is AppRoute.NFTSend -> { createComponentChild( context = context, - params = NFTReceiveComponent.Params(userWalletId = route.userWalletId), - componentFactory = nftReceiveComponentFactory, + params = NFTSendComponent.Params( + userWalletId = route.userWalletId, + nftAsset = route.nftAsset, + nftCollectionName = route.nftCollectionName, + ), + componentFactory = nftSendComponentFactory, ) - is AppRoute.NFTDetails -> - createComponentChild( - context = context, - params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset), - componentFactory = nftDetailsComponentFactory, - ) - is AppRoute.OnboardingNote, - is AppRoute.SaveWallet, - is AppRoute.OnboardingOther, - is AppRoute.OnboardingTwins, - is AppRoute.OnboardingWallet, - -> error("Unsupported route: $route") + } } - // endregion - } - - @Suppress("LongMethod", "CyclomaticComplexMethod") - private fun createChildLegacy(route: AppRoute, contextFactory: (route: AppRoute) -> AppComponentContext): Child { - componentContexts[route] = contextFactory(route) - - // region Child creation - return when (route) { - is AppRoute.Initial -> { - Child.Initial - } - is AppRoute.AccessCodeRecovery -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = accessCodeRecoveryComponentFactory, - ) - } - is AppRoute.AppCurrencySelector -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = appCurrencySelectorComponentFactory, - ) - } - is AppRoute.SaveWallet -> { - route.asFragmentChild(Provider { SaveWalletBottomSheetFragment() }) - } - is AppRoute.Send -> { - if (sendFeatureToggles.isSendV2Enabled) { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = com.tangem.features.send.v2.api.SendComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - transactionId = route.transactionId, - amount = route.amount, - tag = route.tag, - destinationAddress = route.destinationAddress, - ), - componentFactory = sendComponentFactoryV2, - ) - } else { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SendComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - transactionId = route.transactionId, - amount = route.amount, - tag = route.tag, - destinationAddress = route.destinationAddress, - ), - componentFactory = sendComponentFactory, - ) - } - } - is AppRoute.AppSettings -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = appSettingsComponentFactory, - ) - } - is AppRoute.CardSettings -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = CardSettingsComponent.Params(userWalletId = route.userWalletId), - componentFactory = cardSettingsComponentFactory, - ) - } - is AppRoute.Details -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = DetailsComponent.Params(route.userWalletId), - componentFactory = detailsComponentFactory, - ) - } - is AppRoute.DetailsSecurity -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SecurityModeComponent.Params( - userWalletId = route.userWalletId, - ), - componentFactory = securityModeComponentFactory, - ) - } - is AppRoute.Disclaimer -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = DisclaimerComponent.Params(route.isTosAccepted), - componentFactory = disclaimerComponentFactory, - ) - } - is AppRoute.Home -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = homeComponentFactory, - ) - } - is AppRoute.ManageTokens -> { - val source = when (route.source) { - AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS - AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING - AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES - } - - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = ManageTokensComponent.Params(route.userWalletId, source), - componentFactory = manageTokensComponentFactory, - ) - } - is AppRoute.OnboardingNote -> { - route.asFragmentChild(Provider { OnboardingNoteFragment() }) - } - is AppRoute.OnboardingOther -> { - route.asFragmentChild(Provider { OnboardingOtherCardsFragment() }) - } - is AppRoute.OnboardingTwins -> { - route.asFragmentChild(Provider { OnboardingTwinsFragment() }) - } - is AppRoute.OnboardingWallet -> { - route.asFragmentChild(Provider { OnboardingWalletFragment() }) - } - is AppRoute.QrScanning -> { - val source = when (route.source) { - is AppRoute.QrScanning.Source.Send -> SourceType.SEND - is AppRoute.QrScanning.Source.WalletConnect -> SourceType.WALLET_CONNECT - } - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = QrScanningComponent.Params( - source = source, - networkName = (route.source as? AppRoute.QrScanning.Source.Send)?.networkName, - ), - componentFactory = qrScanningComponentFactory, - ) - } - is AppRoute.ReferralProgram -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = ReferralComponent.Params(route.userWalletId), - componentFactory = referralComponentFactory, - ) - } - is AppRoute.ResetToFactory -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = ResetCardComponent.Params( - userWalletId = route.userWalletId, - cardId = route.cardId, - isActiveBackupStatus = route.isActiveBackupStatus, - backupCardsCount = route.backupCardsCount, - ), - componentFactory = resetCardComponentFactory, - ) - } - is AppRoute.Swap -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SwapComponent.Params( - currencyFrom = route.currencyFrom, - currencyTo = route.currencyTo, - userWalletId = route.userWalletId, - isInitialReverseOrder = route.isInitialReverseOrder, - screenSource = route.screenSource, - ), - componentFactory = swapComponentFactory, - ) - } - is AppRoute.Wallet -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = walletComponentFactory, - ) - } - is AppRoute.WalletConnectSessions -> { - if (walletConnectFeatureToggles.isRedesignedWalletConnectEnabled) { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = redesignedWalletConnectComponentFactory, - ) - } else { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = walletConnectComponentFactory, - ) - } - } - is AppRoute.CurrencyDetails -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = TokenDetailsComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - ), - componentFactory = tokenDetailsComponentFactory, - ) - } - is AppRoute.Welcome -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = WelcomeComponent.Params( - intent = route.intent, - ), - componentFactory = welcomeComponentFactory, - ) - } - is AppRoute.TesterMenu -> { - Child.LegacyIntent(testerRouter.getEntryIntent()) - } - is AppRoute.Staking -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = StakingComponent.Params( - userWalletId = route.userWalletId, - cryptoCurrencyId = route.cryptoCurrencyId, - yieldId = route.yieldId, - ), - componentFactory = stakingComponentFactory, - ) - } - is AppRoute.PushNotification -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = Unit, - componentFactory = pushNotificationsComponentFactory, - ) - } - is AppRoute.WalletSettings -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = WalletSettingsComponent.Params(route.userWalletId), - componentFactory = walletSettingsComponentFactory, - ) - } - is AppRoute.MarketsTokenDetails -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = MarketsTokenDetailsComponent.Params( - token = route.token, - appCurrency = route.appCurrency, - showPortfolio = route.showPortfolio, - analyticsParams = route.analyticsParams?.let { - MarketsTokenDetailsComponent.AnalyticsParams( - blockchain = it.blockchain, - source = it.source, - ) - }, - ), - componentFactory = marketsTokenDetailsComponentFactory, - ) - } - is AppRoute.Onramp -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = OnrampComponent.Params( - userWalletId = route.userWalletId, - cryptoCurrency = route.currency, - source = route.source, - ), - componentFactory = onrampComponentFactory, - ) - } - is AppRoute.OnrampSuccess -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = OnrampSuccessComponent.Params(route.externalTxId), - componentFactory = onrampSuccessComponentFactory, - ) - } - is AppRoute.BuyCrypto -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = BuyCryptoComponent.Params(userWalletId = route.userWalletId), - componentFactory = buyCryptoComponentFactory, - ) - } - is AppRoute.SellCrypto -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SellCryptoComponent.Params(userWalletId = route.userWalletId), - componentFactory = sellCryptoComponentFactory, - ) - } - is AppRoute.SwapCrypto -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = SwapSelectTokensComponent.Params(userWalletId = route.userWalletId), - componentFactory = swapSelectTokensComponentFactory, - ) - } - is AppRoute.Onboarding -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = OnboardingEntryComponent.Params( - scanResponse = route.scanResponse, - mode = when (route.mode) { - AppRoute.Onboarding.Mode.Onboarding -> OnboardingEntryComponent.Mode.Onboarding - AppRoute.Onboarding.Mode.AddBackupWallet1 -> OnboardingEntryComponent.Mode.AddBackupWallet1 - AppRoute.Onboarding.Mode.WelcomeOnlyTwin -> OnboardingEntryComponent.Mode.WelcomeOnlyTwin - AppRoute.Onboarding.Mode.RecreateWalletTwin -> - OnboardingEntryComponent.Mode.RecreateWalletTwin - AppRoute.Onboarding.Mode.ContinueFinalize -> - OnboardingEntryComponent.Mode.ContinueFinalize - }, - ), - componentFactory = onboardingEntryComponentFactory, - ) - } - is AppRoute.Stories -> { - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = StoriesComponent.Params( - storyId = route.storyId, - nextScreen = route.nextScreen, - screenSource = route.screenSource, - ), - componentFactory = storiesComponentFactory, - ) - } - is AppRoute.NFTCollections -> - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = NFTCollectionsComponent.Params(userWalletId = route.userWalletId), - componentFactory = nftCollectionsComponentFactory, - ) - is AppRoute.NFTReceive -> - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = NFTReceiveComponent.Params(userWalletId = route.userWalletId), - componentFactory = nftReceiveComponentFactory, - ) - is AppRoute.NFTDetails -> - route.asComponentChild( - contextProvider = contextProvider(route, contextFactory), - params = NFTDetailsComponent.Params(userWalletId = route.userWalletId, nftAsset = route.nftAsset), - componentFactory = nftDetailsComponentFactory, - ) - } - // endregion - } - - fun doOnDestroy() { - componentContexts.clear() - } - - private fun contextProvider( - appRoute: AppRoute, - contextFactory: (route: AppRoute) -> AppComponentContext, - ): Provider = Provider { - componentContexts.getOrPut(appRoute) { contextFactory(appRoute) } - } - - private companion object { - - val componentContexts = WeakHashMap() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt b/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt index f20d2b096b..6a9fac9177 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt @@ -1,43 +1,9 @@ package com.tangem.tap.routing.utils -import androidx.fragment.app.Fragment -import com.tangem.common.routing.AppRoute -import com.tangem.common.routing.bundle.RouteBundleParams import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.tap.DecomposeFragment import com.tangem.tap.routing.component.RoutingComponent.Child -import com.tangem.utils.Provider - -internal fun AppRoute.asFragmentChild(fragmentProvider: Provider): Child { - val provider = Provider { - val bundle = (this as? RouteBundleParams)?.getBundle() - - fragmentProvider().apply { - arguments = bundle - } - } - - return Child.LegacyFragment(path, provider) -} - -internal fun > AppRoute.asComponentChild( - contextProvider: Provider, - params: P, - componentFactory: F, -): Child { - val fragmentProvider = Provider { - DecomposeFragment.newInstance( - tag = path, - contextProvider = contextProvider, - params = params, - componentFactory = componentFactory, - ) - } - - return Child.LegacyFragment(path, fragmentProvider) -} internal fun > createComponentChild( context: AppComponentContext, diff --git a/app/src/main/res/color/selector_btn_content_primary.xml b/app/src/main/res/color/selector_btn_content_primary.xml deleted file mode 100644 index 70f6aded83..0000000000 --- a/app/src/main/res/color/selector_btn_content_primary.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color/selector_btn_content_secondary.xml b/app/src/main/res/color/selector_btn_content_secondary.xml deleted file mode 100644 index 41aa7c843c..0000000000 --- a/app/src/main/res/color/selector_btn_content_secondary.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color/selector_btn_primary.xml b/app/src/main/res/color/selector_btn_primary.xml deleted file mode 100644 index 6e8aab4c79..0000000000 --- a/app/src/main/res/color/selector_btn_primary.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color/selector_btn_secondary.xml b/app/src/main/res/color/selector_btn_secondary.xml deleted file mode 100644 index 47a88e3645..0000000000 --- a/app/src/main/res/color/selector_btn_secondary.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/color/selector_chip_shop.xml b/app/src/main/res/color/selector_chip_shop.xml deleted file mode 100644 index 052f962892..0000000000 --- a/app/src/main/res/color/selector_chip_shop.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_chip_shop_text.xml b/app/src/main/res/color/selector_chip_shop_text.xml deleted file mode 100644 index 5d5afc33ff..0000000000 --- a/app/src/main/res/color/selector_chip_shop_text.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_chip_stroke.xml b/app/src/main/res/color/selector_chip_stroke.xml deleted file mode 100644 index c2827e783e..0000000000 --- a/app/src/main/res/color/selector_chip_stroke.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_chip_text.xml b/app/src/main/res/color/selector_chip_text.xml deleted file mode 100644 index e205f4e82d..0000000000 --- a/app/src/main/res/color/selector_chip_text.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_edit_text.xml b/app/src/main/res/color/selector_edit_text.xml deleted file mode 100644 index 192514f802..0000000000 --- a/app/src/main/res/color/selector_edit_text.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/selector_edit_text_secondary.xml b/app/src/main/res/color/selector_edit_text_secondary.xml deleted file mode 100644 index df06469793..0000000000 --- a/app/src/main/res/color/selector_edit_text_secondary.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-de/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable-de/buy_with_googlepay_button_content.xml deleted file mode 100755 index e1e2577110..0000000000 --- a/app/src/main/res/drawable-de/buy_with_googlepay_button_content.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable-fr/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable-fr/buy_with_googlepay_button_content.xml deleted file mode 100755 index 4be47da569..0000000000 --- a/app/src/main/res/drawable-fr/buy_with_googlepay_button_content.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable-hdpi/card_placeholder_wallet.9.png b/app/src/main/res/drawable-hdpi/card_placeholder_wallet.9.png deleted file mode 100644 index 97ea9bc260..0000000000 Binary files a/app/src/main/res/drawable-hdpi/card_placeholder_wallet.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-it/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable-it/buy_with_googlepay_button_content.xml deleted file mode 100755 index 477793d6e5..0000000000 --- a/app/src/main/res/drawable-it/buy_with_googlepay_button_content.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable-ru/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable-ru/buy_with_googlepay_button_content.xml deleted file mode 100755 index a624bdabf1..0000000000 --- a/app/src/main/res/drawable-ru/buy_with_googlepay_button_content.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable-xhdpi/googlepay_button_no_shadow_background_image.9.png b/app/src/main/res/drawable-xhdpi/googlepay_button_no_shadow_background_image.9.png deleted file mode 100755 index 969cc52a94..0000000000 Binary files a/app/src/main/res/drawable-xhdpi/googlepay_button_no_shadow_background_image.9.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/card_placeholder_wallet.9.png b/app/src/main/res/drawable-xxhdpi/card_placeholder_wallet.9.png index 7483c7232b..4c28ecafd2 100644 Binary files a/app/src/main/res/drawable-xxhdpi/card_placeholder_wallet.9.png and b/app/src/main/res/drawable-xxhdpi/card_placeholder_wallet.9.png differ diff --git a/app/src/main/res/drawable/buy_with_googlepay_button_content.xml b/app/src/main/res/drawable/buy_with_googlepay_button_content.xml deleted file mode 100755 index a893cd0cac..0000000000 --- a/app/src/main/res/drawable/buy_with_googlepay_button_content.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/card_placeholder_white.xml b/app/src/main/res/drawable/card_placeholder_white.xml deleted file mode 100644 index d980dc5599..0000000000 --- a/app/src/main/res/drawable/card_placeholder_white.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/googlepay_button_no_shadow_background.xml b/app/src/main/res/drawable/googlepay_button_no_shadow_background.xml deleted file mode 100755 index 9543c4bcb6..0000000000 --- a/app/src/main/res/drawable/googlepay_button_no_shadow_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/googlepay_button_overlay.xml b/app/src/main/res/drawable/googlepay_button_overlay.xml deleted file mode 100755 index 70c7a92271..0000000000 --- a/app/src/main/res/drawable/googlepay_button_overlay.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_access_code.xml b/app/src/main/res/drawable/ic_access_code.xml deleted file mode 100644 index d24768aa15..0000000000 --- a/app/src/main/res/drawable/ic_access_code.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable/ic_angle_bracket_up.xml b/app/src/main/res/drawable/ic_angle_bracket_up.xml deleted file mode 100644 index 5311e45d76..0000000000 --- a/app/src/main/res/drawable/ic_angle_bracket_up.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/ic_arrow_refresh.xml b/app/src/main/res/drawable/ic_arrow_refresh.xml deleted file mode 100644 index 1acc645cec..0000000000 --- a/app/src/main/res/drawable/ic_arrow_refresh.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml deleted file mode 100644 index b33d1ca408..0000000000 --- a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_dot.xml b/app/src/main/res/drawable/ic_dot.xml deleted file mode 100644 index bef54d1532..0000000000 --- a/app/src/main/res/drawable/ic_dot.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_selected_dot.xml b/app/src/main/res/drawable/ic_selected_dot.xml deleted file mode 100644 index f747ab54ba..0000000000 --- a/app/src/main/res/drawable/ic_selected_dot.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/img_onboarding_success.xml b/app/src/main/res/drawable/img_onboarding_success.xml deleted file mode 100644 index 84574803ea..0000000000 --- a/app/src/main/res/drawable/img_onboarding_success.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/progress_rounded_shape.xml b/app/src/main/res/drawable/progress_rounded_shape.xml deleted file mode 100644 index 7be741d331..0000000000 --- a/app/src/main/res/drawable/progress_rounded_shape.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_tabs_dots.xml b/app/src/main/res/drawable/selector_tabs_dots.xml deleted file mode 100644 index 8b1678434a..0000000000 --- a/app/src/main/res/drawable/selector_tabs_dots.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_circle.xml b/app/src/main/res/drawable/shape_circle.xml deleted file mode 100644 index 59a662d141..0000000000 --- a/app/src/main/res/drawable/shape_circle.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_ellipse.xml b/app/src/main/res/drawable/shape_ellipse.xml deleted file mode 100644 index ca6cad24b5..0000000000 --- a/app/src/main/res/drawable/shape_ellipse.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rectangle_rounded_8.xml b/app/src/main/res/drawable/shape_rectangle_rounded_8.xml deleted file mode 100644 index 4f20cb0e64..0000000000 --- a/app/src/main/res/drawable/shape_rectangle_rounded_8.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable/shape_refresh_button.xml b/app/src/main/res/drawable/shape_refresh_button.xml deleted file mode 100644 index cade7f63f3..0000000000 --- a/app/src/main/res/drawable/shape_refresh_button.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_success_circle.xml b/app/src/main/res/drawable/shape_success_circle.xml deleted file mode 100644 index d6c04dd519..0000000000 --- a/app/src/main/res/drawable/shape_success_circle.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml b/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml deleted file mode 100644 index 131299d4d5..0000000000 --- a/app/src/main/res/layout-h680dp/layout_onboarding_container_bottom.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index dac7964d66..0000000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/btn_expand_collapse.xml b/app/src/main/res/layout/btn_expand_collapse.xml deleted file mode 100644 index 8d9c220409..0000000000 --- a/app/src/main/res/layout/btn_expand_collapse.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/buy_with_googlepay_button.xml b/app/src/main/res/layout/buy_with_googlepay_button.xml deleted file mode 100755 index 2553181fd8..0000000000 --- a/app/src/main/res/layout/buy_with_googlepay_button.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout/dialog_onboarding_address_info.xml b/app/src/main/res/layout/dialog_onboarding_address_info.xml deleted file mode 100644 index 258b3fbbcc..0000000000 --- a/app/src/main/res/layout/dialog_onboarding_address_info.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_onboarding_main.xml b/app/src/main/res/layout/fragment_onboarding_main.xml deleted file mode 100644 index b184df2cd2..0000000000 --- a/app/src/main/res/layout/fragment_onboarding_main.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml deleted file mode 100644 index 98ad59717a..0000000000 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_backup_info_adapter.xml b/app/src/main/res/layout/item_backup_info_adapter.xml deleted file mode 100644 index 46b359a8f1..0000000000 --- a/app/src/main/res/layout/item_backup_info_adapter.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_backup_access_code.xml b/app/src/main/res/layout/layout_backup_access_code.xml deleted file mode 100644 index f402774903..0000000000 --- a/app/src/main/res/layout/layout_backup_access_code.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_backup_access_code_info.xml b/app/src/main/res/layout/layout_backup_access_code_info.xml deleted file mode 100644 index 5095c1eca0..0000000000 --- a/app/src/main/res/layout/layout_backup_access_code_info.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_backup_access_code_submit.xml b/app/src/main/res/layout/layout_backup_access_code_submit.xml deleted file mode 100644 index 58b97cc2a6..0000000000 --- a/app/src/main/res/layout/layout_backup_access_code_submit.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_buttons_add_cards.xml b/app/src/main/res/layout/layout_onboarding_buttons_add_cards.xml deleted file mode 100644 index a6fd01cc5b..0000000000 --- a/app/src/main/res/layout/layout_onboarding_buttons_add_cards.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_buttons_common.xml b/app/src/main/res/layout/layout_onboarding_buttons_common.xml deleted file mode 100644 index 2e53801d0c..0000000000 --- a/app/src/main/res/layout/layout_onboarding_buttons_common.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_container_bottom.xml b/app/src/main/res/layout/layout_onboarding_container_bottom.xml deleted file mode 100644 index 42668c1561..0000000000 --- a/app/src/main/res/layout/layout_onboarding_container_bottom.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/layout_onboarding_container_top.xml b/app/src/main/res/layout/layout_onboarding_container_top.xml deleted file mode 100644 index d3708a8bfc..0000000000 --- a/app/src/main/res/layout/layout_onboarding_container_top.xml +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_main.xml b/app/src/main/res/layout/layout_onboarding_main.xml deleted file mode 100644 index 29b34f187f..0000000000 --- a/app/src/main/res/layout/layout_onboarding_main.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_manage_tokens.xml b/app/src/main/res/layout/layout_onboarding_manage_tokens.xml deleted file mode 100644 index cf08eaf50b..0000000000 --- a/app/src/main/res/layout/layout_onboarding_manage_tokens.xml +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_onboarding_seed_phrase.xml b/app/src/main/res/layout/layout_onboarding_seed_phrase.xml deleted file mode 100644 index 017730394f..0000000000 --- a/app/src/main/res/layout/layout_onboarding_seed_phrase.xml +++ /dev/null @@ -1,5 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_pseudo_toolbar.xml b/app/src/main/res/layout/layout_pseudo_toolbar.xml deleted file mode 100644 index 361f8cbb40..0000000000 --- a/app/src/main/res/layout/layout_pseudo_toolbar.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/layout_warning.xml b/app/src/main/res/layout/layout_warning.xml deleted file mode 100644 index 9c19796c5b..0000000000 --- a/app/src/main/res/layout/layout_warning.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/layout_warning_card_action.xml b/app/src/main/res/layout/layout_warning_card_action.xml deleted file mode 100644 index 3b9177f441..0000000000 --- a/app/src/main/res/layout/layout_warning_card_action.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - -