From 5036f8a383293acf05101fee15f086e53b465c10 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 6 Oct 2021 22:30:19 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 4 +- .../com/tangem/tap/common/DialogManager.kt | 7 +- .../tap/common/extensions/WalletManager.kt | 6 +- .../com/tangem/tap/common/redux/AppReducer.kt | 2 - .../com/tangem/tap/common/redux/AppState.kt | 4 - .../tangem/tap/common/redux/StateDialog.kt | 7 + .../tap/common/redux/global/GlobalAction.kt | 17 +- .../common/redux/global/GlobalMidlleware.kt | 13 +- .../tap/common/redux/global/GlobalReducer.kt | 6 + .../tap/common/redux/global/GlobalState.kt | 4 +- .../com/tangem/tap/domain/TangemSdkManager.kt | 21 +- .../com/tangem/tap/domain/TapWalletManager.kt | 19 +- .../com/tangem/tap/domain/TapWorkarounds.kt | 2 + .../tangem/tap/domain/tasks/ScanNoteTask.kt | 2 +- .../CreateProductWalletAndRescanTask.kt | 139 +++++++++ .../tasks/product/ProductCommandProcessor.kt | 16 + .../domain/tasks/product/ScanProductTask.kt | 141 +++++++++ .../tangem/tap/features/home/HomeFragment.kt | 12 +- .../tap/features/home/redux/HomeMiddleware.kt | 109 ++++--- .../AddressInfoBottomSheetDialog.kt | 44 +++ .../features/onboarding/OnboardingService.kt | 168 ---------- .../products/BaseOnboardingFragment.kt | 80 +++++ .../products/note/OnboardingNoteFragment.kt | 146 +++++---- .../note/redux/OnboardingNoteAction.kt | 21 +- .../note/redux/OnboardingNoteMiddleware.kt | 108 +++++-- .../note/redux/OnboardingNoteReducer.kt | 51 ++- .../note/redux/OnboardingNoteState.kt | 32 +- .../OnboardingOtherCardsFragment.kt | 19 +- .../wallet/OnboardingWalletFragment.kt | 11 - .../wallet/redux/OnboardingWalletState.kt | 3 +- .../onboarding/redux/OnboardingAction.kt | 22 -- .../onboarding/redux/OnboardingMiddleware.kt | 22 -- .../onboarding/redux/OnboardingReducer.kt | 24 -- .../onboarding/redux/OnboardingState.kt | 11 - .../onboarding/redux/StateSupportClasses.kt | 26 -- .../onboarding/service/OnboardingHelper.kt | 33 ++ .../service/OnboardingNoteService.kt | 21 ++ .../service/ProductOnboardingService.kt | 295 ++++++++++++++++++ .../tap/features/send/BaseStoreFragment.kt | 3 + 39 files changed, 1168 insertions(+), 503 deletions(-) create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt create mode 100644 app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt rename app/src/main/java/com/tangem/tap/features/onboarding/products/{old => otherCards}/OnboardingOtherCardsFragment.kt (62%) delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt delete mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt create mode 100644 app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 37682e2bc8..49856db42b 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -75,8 +75,8 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 val isScannedBefore = store.state.globalState.scanNoteResponse != null - val isOnboardingStarted = store.state.onboardingState.onboardingData?.scanNoteResponse != null - if ((backStackIsEmpty || isScannedBefore) && !isOnboardingStarted) { + val isOnboardingServiceActive = store.state.globalState.onboardingService != null + if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) } intentHandler.handleIntent(intent) 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 218c96776f..114c17c7cd 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -6,8 +6,7 @@ import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.features.details.redux.walletconnect.WalletConnectDialog import com.tangem.tap.features.details.ui.walletconnect.dialogs.* -import com.tangem.tap.features.onboarding.products.note.OnboardingAddressInfoDialog -import com.tangem.tap.features.onboarding.redux.OnboardingAction +import com.tangem.tap.features.onboarding.AddressInfoBottomSheetDialog import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -43,6 +42,7 @@ class DialogManager : StoreSubscriber { dialog = when (state.dialog) { is AppDialog.ScanFailsDialog -> ScanFailsDialog.create(context) + is AppDialog.AddressInfoDialog -> AddressInfoBottomSheetDialog(state.dialog, context) is WalletConnectDialog.UnsupportedCard -> SimpleAlertDialog.create( titleRes = R.string.wallet_connect, @@ -71,9 +71,6 @@ class DialogManager : StoreSubscriber { TransactionDialog.create(state.dialog.dialogData, context) is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context) - is OnboardingAction.Dialog.AddressInfo -> { - OnboardingAddressInfoDialog(state.dialog, context) - } else -> null } dialog?.show() diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index d8b5b136dd..683f1574a3 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -12,7 +12,7 @@ import com.tangem.tap.network.NetworkConnectivity /** [REDACTED_AUTHOR] */ -suspend fun WalletManager.loadWalletData(): Result = try { +suspend fun WalletManager.safeUpdate(): Result = try { update() Result.Success(wallet) } catch (exception: Exception) { @@ -23,10 +23,10 @@ suspend fun WalletManager.loadWalletData(): Result = try { val amountToCreateAccount = blockchain.amountToCreateAccount(wallet.getFirstToken()) if (blockchain.isNoAccountError(exception) && amountToCreateAccount != null) { - Result.Failure(TapError.NoAccount(amountToCreateAccount.toString())) + Result.Failure(TapError.WalletManagerUpdate.NoAccountError(amountToCreateAccount.toString())) } else { val message = exception.localizedMessage ?: "Unknown exception during WalletManager update" - Result.Failure(TapError.CustomError(message)) + Result.Failure(TapError.WalletManagerUpdate.InternalError(message)) } } } \ No newline at end of file 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 e60b9cade1..4de2f24938 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 @@ -8,7 +8,6 @@ import com.tangem.tap.features.disclaimer.redux.DisclaimerReducer import com.tangem.tap.features.home.redux.HomeReducer import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteReducer import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletReducer -import com.tangem.tap.features.onboarding.redux.OnboardingReducer import com.tangem.tap.features.send.redux.reducers.SendScreenReducer import com.tangem.tap.features.tokens.redux.TokensReducer import com.tangem.tap.features.wallet.redux.reducers.WalletReducer @@ -22,7 +21,6 @@ fun appReducer(action: Action, state: AppState?): AppState { navigationState = NavigationReducer.reduce(action, state), globalState = globalReducer(action, state), homeState = HomeReducer.reduce(action, state), - onboardingState = OnboardingReducer.reduce(action, state), onboardingNoteState = OnboardingNoteReducer.reduce(action, state), onboardingWalletState = OnboardingWalletReducer.reduce(action, state), walletState = WalletReducer.reduce(action, state), 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 985a2a75ba..ba9aa021d1 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 @@ -16,8 +16,6 @@ import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteMidd import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.redux.OnboardingMiddleware -import com.tangem.tap.features.onboarding.redux.OnboardingState import com.tangem.tap.features.send.redux.middlewares.SendMiddleware import com.tangem.tap.features.send.redux.states.SendState import com.tangem.tap.features.tokens.redux.TokensMiddleware @@ -31,7 +29,6 @@ data class AppState( val navigationState: NavigationState = NavigationState(), val globalState: GlobalState = GlobalState(), val homeState: HomeState = HomeState(), - val onboardingState: OnboardingState = OnboardingState(), val onboardingNoteState: OnboardingNoteState = OnboardingNoteState(), val onboardingWalletState: OnboardingWalletState = OnboardingWalletState(), val walletState: WalletState = WalletState(), @@ -48,7 +45,6 @@ data class AppState( logMiddleware, navigationMiddleware, notificationsMiddleware, GlobalMiddleware.handler, HomeMiddleware.handler, - OnboardingMiddleware.handler, OnboardingNoteMiddleware.handler, OnboardingWalletMiddleware.handler, WalletMiddleware().walletMiddleware, diff --git a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt index e643ceb207..36e1a749a5 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/StateDialog.kt @@ -1,5 +1,7 @@ package com.tangem.tap.common.redux +import com.tangem.tap.features.wallet.redux.AddressData + /** [REDACTED_AUTHOR] */ @@ -7,4 +9,9 @@ interface StateDialog sealed class AppDialog : StateDialog { object ScanFailsDialog : AppDialog() + data class AddressInfoDialog( + val addressData: AddressData, + val onCopyAddress: () -> Unit, + val onExploreAddress: () -> Unit + ) : AppDialog() } \ 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 4c4675eb96..4021557fac 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 @@ -3,10 +3,7 @@ package com.tangem.tap.common.redux.global import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError -import com.tangem.tap.common.redux.DebugErrorAction -import com.tangem.tap.common.redux.NotificationAction -import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.common.redux.ToastNotificationAction +import com.tangem.tap.common.redux.* import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage @@ -15,6 +12,7 @@ import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.feedback.EmailData import com.tangem.tap.features.feedback.FeedbackManager +import com.tangem.tap.features.onboarding.service.ProductOnboardingService import com.tangem.tap.network.moonpay.MoonPayUserStatus import org.rekotlin.Action @@ -23,16 +21,21 @@ sealed class GlobalAction : Action { // notifications data class ShowNotification(override val messageResource: Int) : GlobalAction(), NotificationAction data class ShowToastNotification(override val messageResource: Int) : GlobalAction(), ToastNotificationAction + 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() - data class DebugShowError(override val error: TapError) : GlobalAction(), DebugErrorAction + sealed class Onboarding { + data class Activate(val onboardingService: ProductOnboardingService) : GlobalAction() + object Deactivate : GlobalAction() + } data class ReadCard( - val onSuccess: (suspend (ScanNoteResponse) -> Unit)? = null, - val onFailure: (suspend (TangemError) -> Unit)? = null, + val onSuccess: ((ScanNoteResponse) -> Unit)? = null, + val onFailure: ((TangemError) -> Unit)? = null, val messageResId: Int? = null, ) : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index f6e8152a77..2f8cbfffc4 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -6,6 +6,7 @@ import com.tangem.common.services.Result import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain +import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager @@ -16,9 +17,7 @@ import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.rekotlin.Middleware class GlobalMiddleware { @@ -73,7 +72,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } is GlobalAction.UpdateFeedbackInfo -> { store.state.globalState.feedbackManager?.infoHolder - ?.setWalletsInfo(action.walletManagers) + ?.setWalletsInfo(action.walletManagers) } is GlobalAction.GetMoonPayUserStatus -> { val apiKey = appState()?.globalState?.configManager?.config?.moonPayApiKey @@ -82,7 +81,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState val userStatusResponse = MoonpayService().getUserStatus(apiKey) if (userStatusResponse is Result.Success) { store.dispatchOnMain( - GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data) + GlobalAction.GetMoonPayUserStatus.Success(userStatusResponse.data) ) } } @@ -90,9 +89,9 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } is GlobalAction.ReadCard -> { scope.launch { - val result = tangemSdkManager.scanNote(FirebaseAnalyticsHandler, action.messageResId) - withContext(Dispatchers.Main) { - store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId) + store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + withMainContext { when (result) { is CompletionResult.Success -> { tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data.card) 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 e1f1fa2217..380fe6847f 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 @@ -10,6 +10,12 @@ fun globalReducer(action: Action, state: AppState): GlobalState { val globalState = state.globalState return when (action) { + is GlobalAction.Onboarding.Activate -> { + globalState.copy(onboardingService = action.onboardingService) + } + is GlobalAction.Onboarding.Deactivate -> { + globalState.copy(onboardingService = null) + } is GlobalAction.ScanFailsCounter.Increment -> { globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1) } diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 558dcaab95..0753b6d472 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -8,6 +8,7 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.features.feedback.FeedbackManager +import com.tangem.tap.features.onboarding.service.ProductOnboardingService import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import com.tangem.tap.network.moonpay.MoonPayUserStatus import org.rekotlin.StateType @@ -24,7 +25,8 @@ data class GlobalState( val appCurrency: FiatCurrencyName = DEFAULT_FIAT_CURRENCY, val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, - val moonPayUserStatus: MoonPayUserStatus? = null + val moonPayUserStatus: MoonPayUserStatus? = null, + val onboardingService: ProductOnboardingService? = null, ) : StateType typealias CryptoCurrencyName = String diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index adc0404f24..4cc78b775b 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -1,5 +1,6 @@ package com.tangem.tap.domain +import CreateProductWalletAndRescanTask import android.content.Context import com.tangem.Message import com.tangem.TangemSdk @@ -22,6 +23,7 @@ import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask import com.tangem.tap.domain.tasks.ScanNoteResponse import com.tangem.tap.domain.tasks.ScanNoteTask +import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers @@ -31,8 +33,10 @@ import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { + @Deprecated("Use can product") suspend fun scanNote( - analyticsHandler: AnalyticsHandler, messageRes: Int? = null, + analyticsHandler: AnalyticsHandler, + messageRes: Int? = null, ): CompletionResult { analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) @@ -42,6 +46,21 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ).also { sendScanFailuresToAnalytics(analyticsHandler, it) } } + suspend fun scanProduct( + analyticsHandler: AnalyticsHandler, + messageRes: Int? = null, + ): CompletionResult { + analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) + + val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) + return runTaskAsyncReturnOnMain(ScanProductTask(), null, message) + .also { sendScanFailuresToAnalytics(analyticsHandler, it) } + } + + suspend fun createProductWallet(cardId: String?): CompletionResult { + return runTaskAsync(CreateProductWalletAndRescanTask(), cardId, Message(context.getString(R.string.initial_message_create_wallet_body))) + } + private fun sendScanFailuresToAnalytics( analyticsHandler: AnalyticsHandler, result: CompletionResult 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 6e4dec24f5..507db2ad21 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -6,7 +6,7 @@ import com.tangem.common.services.Result import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchDebugErrorNotification -import com.tangem.tap.common.extensions.loadWalletData +import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.FiatCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.currenciesRepository @@ -40,22 +40,17 @@ class TapWalletManager { by lazy { WalletManagerFactory(blockchainSdkConfig) } suspend fun loadWalletData(walletManager: WalletManager) { - handleUpdateWalletResult(walletManager.loadWalletData(), walletManager) + handleUpdateWalletResult(walletManager.safeUpdate(), walletManager) } suspend fun updateWallet(walletManager: WalletManager) { - val result = try { - walletManager.update() - Result.Success(walletManager.wallet) - } catch (exception: Exception) { - Result.Failure(exception) - } + val result = walletManager.safeUpdate() withContext(Dispatchers.Main) { when (result) { is Result.Success -> store.dispatch(WalletAction.UpdateWallet.Success(result.data)) is Result.Failure -> - store.dispatch(WalletAction.UpdateWallet.Failure(result.error?.localizedMessage)) + store.dispatch(WalletAction.UpdateWallet.Failure(result.error.localizedMessage)) } } @@ -262,11 +257,11 @@ class TapWalletManager { TapError.NoInternetConnection -> { store.dispatch(WalletAction.LoadData.Failure(error)) } - is TapError.NoAccount -> { + is TapError.WalletManagerUpdate.NoAccountError -> { store.dispatch(WalletAction.LoadWallet - .NoAccount(walletManager.wallet, error.amountToCreateAccount)) + .NoAccount(walletManager.wallet, error.customMessage)) } - is TapError.CustomError -> { + is TapError.WalletManagerUpdate.InternalError -> { store.dispatch(WalletAction.LoadWallet .Failure(walletManager.wallet, error.customMessage)) } diff --git a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt index ffc138b170..9003c9f4fc 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWorkarounds.kt @@ -60,6 +60,8 @@ object TapWorkarounds { ) } +val DELAY_SDK_DIALOG_CLOSE = 1400L + val Card.isMultiwalletAllowed: Boolean get() { return !isTangemTwin() && !isStart2Coin && !isTangemNote() diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt index 510d2785d8..39f5d63af0 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/ScanNoteTask.kt @@ -67,7 +67,7 @@ class ScanNoteTask(val card: Card? = null) : CardSessionRunnable= FirmwareVersion.MultiWalletAvailable) { + } else if (!card.isTangemNote() && card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable) { createMissingWalletsIfNeeded(card, session, callback) } else { callback(CompletionResult.Success( diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt new file mode 100644 index 0000000000..6134e5de55 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletAndRescanTask.kt @@ -0,0 +1,139 @@ +import com.tangem.common.CompletionResult +import com.tangem.common.card.Card +import com.tangem.common.card.EllipticCurve +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.guard +import com.tangem.operations.PreflightReadMode +import com.tangem.operations.PreflightReadTask +import com.tangem.operations.wallet.CreateWalletCommand +import com.tangem.operations.wallet.CreateWalletResponse +import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain +import com.tangem.tap.domain.TapWorkarounds.isTangemNote +import com.tangem.tap.domain.TapWorkarounds.isTangemWallet +import com.tangem.tap.domain.tasks.product.ProductCommandProcessor +import com.tangem.tap.domain.twins.isTangemTwin + +/** +[REDACTED_AUTHOR] + */ +class CreateProductWalletAndRescanTask : CardSessionRunnable { + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + CreateProductWalletTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run(session, callback) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} + +private class CreateProductWalletTask : CardSessionRunnable { + + override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) { + val card = session.environment.card.guard { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + + val commandProcessor = when { + card.isTangemNote() -> CreateWalletTangemNote() + card.isTangemTwin() -> CreateWalletTangemTwin() + card.isTangemWallet() -> CreateWalletTangemWallet() + else -> CreateWalletOtherCards() + } + commandProcessor.proceed(card, session, callback) + } +} + +private class CreateWalletTangemNote : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + if (card.supportedCurves.isEmpty()) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + + val curvesSupportedByBlockchain = card.getTangemNoteBlockchain()?.getSupportedCurves() + if (curvesSupportedByBlockchain == null || curvesSupportedByBlockchain.isEmpty()) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + return + } + + val intersectCurves = card.supportedCurves.intersect(curvesSupportedByBlockchain).toList() + if (intersectCurves.isEmpty()) { + callback(CompletionResult.Failure(TangemSdkError.CardError())) + } else { + val curve = if (intersectCurves.contains(EllipticCurve.Secp256k1)) { + EllipticCurve.Secp256k1 + } else { + intersectCurves[0] + } + CreateWalletCommand(curve).run(session, callback) + + } + } +} + +private class CreateWalletTangemWallet : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + throw UnsupportedOperationException() + } +} + +private class CreateWalletTangemTwin : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + throw UnsupportedOperationException() + } +} + +private class CreateWalletOtherCards : ProductCommandProcessor { + + private var index = 0 + + private val curves = EllipticCurve.values().toList() + + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + val curve = curves[index] + createWallet(curve, session, callback) + } + + private fun createWallet( + curve: EllipticCurve, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + CreateWalletCommand(curve).run(session) { result -> + when (result) { + is CompletionResult.Success -> { + if (index == curves.lastIndex) { + callback(CompletionResult.Success(result.data)) + return@run + } + index += 1 + createWallet(curves[index], session, callback) + } + is CompletionResult.Failure -> { + callback(CompletionResult.Failure(result.error)) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt new file mode 100644 index 0000000000..c0810ff887 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ProductCommandProcessor.kt @@ -0,0 +1,16 @@ +package com.tangem.tap.domain.tasks.product + +import com.tangem.common.CompletionResult +import com.tangem.common.card.Card +import com.tangem.common.core.CardSession + +/** +[REDACTED_AUTHOR] + */ +interface ProductCommandProcessor { + fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt new file mode 100644 index 0000000000..710e6e99ff --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -0,0 +1,141 @@ +package com.tangem.tap.domain.tasks.product + +import com.tangem.* +import com.tangem.common.CompletionResult +import com.tangem.common.card.Card +import com.tangem.common.core.CardSession +import com.tangem.common.core.CardSessionRunnable +import com.tangem.common.core.TangemError +import com.tangem.common.extensions.toHexString +import com.tangem.operations.ScanTask +import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand +import com.tangem.tap.domain.TapSdkError +import com.tangem.tap.domain.TapWorkarounds.isExcluded +import com.tangem.tap.domain.TapWorkarounds.isTangemNote +import com.tangem.tap.domain.TapWorkarounds.isTangemWallet +import com.tangem.tap.domain.extensions.getSingleWallet +import com.tangem.tap.domain.tasks.CreateWalletsTask +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.twins.TwinCardsManager +import com.tangem.tap.domain.twins.isTangemTwin + +class ScanProductTask(val card: Card? = null) : CardSessionRunnable { + + override fun run( + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) { + ScanTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> { + val card = this.card ?: result.data + + val error = getErrorIfExcludedCard(card) + if (error != null) { + callback(CompletionResult.Failure(error)) + return@run + } + + val commandProcessor = when { + card.isTangemNote() -> ScanNoteProcessor() + card.isTangemTwin() -> ScanTwinProcessor() + card.isTangemWallet() -> ScanWalletProcessor() + else -> ScanOtherCardsProcessor() + } + commandProcessor.proceed(card, session, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + + private fun getErrorIfExcludedCard(card: Card): TangemError? { + if (card.isExcluded()) return TapSdkError.CardForDifferentApp + + return null + } +} + +private class ScanNoteProcessor : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + callback(CompletionResult.Success(ScanNoteResponse(card, session.environment.walletData))) + } +} + +private class ScanWalletProcessor : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit + ) { + callback(CompletionResult.Success(ScanNoteResponse(card, session.environment.walletData))) + } +} + +private class ScanTwinProcessor : ProductCommandProcessor { + override fun proceed( + card: Card, + session: CardSession, + callback: (result: CompletionResult) -> Unit, + ) { + ReadIssuerDataCommand().run(session) { readDataResult -> + when (readDataResult) { + is CompletionResult.Success -> { + val publicKey = card.getSingleWallet()?.publicKey + if (publicKey == null) { + callback(CompletionResult.Success(ScanNoteResponse(card, null))) + return@run + } + val verified = TwinCardsManager.verifyTwinPublicKey( + readDataResult.data.issuerData, publicKey + ) + if (verified) { + val twinPublicKey = readDataResult.data.issuerData.sliceArray(0 until 65) + callback(CompletionResult.Success( + ScanNoteResponse( + card = card, + walletData = session.environment.walletData, + secondTwinPublicKey = twinPublicKey.toHexString()) + )) + return@run + } else { + callback(CompletionResult.Success(ScanNoteResponse(card, null))) + } + } + is CompletionResult.Failure -> + callback(CompletionResult.Success(ScanNoteResponse(card, null))) + } + } + } + +} + +private class ScanOtherCardsProcessor : ProductCommandProcessor { + override fun proceed(card: Card, session: CardSession, callback: (result: CompletionResult) -> Unit) { + val walletData = session.environment.walletData + if (card.wallets.isEmpty()) { + callback(CompletionResult.Success(ScanNoteResponse(card, walletData))) + return + } + + val curvesPresent = card.wallets.map { it.curve } + val curvesToCreate = card.supportedCurves.subtract(curvesPresent) + + if (curvesToCreate.isEmpty()) { + callback(CompletionResult.Success(ScanNoteResponse(card, walletData))) + return + } + + CreateWalletsTask().run(session) { result -> + when (result) { + is CompletionResult.Success -> + callback(CompletionResult.Success(ScanNoteResponse(result.data, walletData))) + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 7633a60893..71e0b7a83c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -15,6 +15,7 @@ import com.tangem.tap.features.home.redux.HomeState import com.tangem.tap.store import com.tangem.wallet.R import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* import org.rekotlin.StoreSubscriber import java.lang.ref.WeakReference @@ -40,10 +41,15 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber = { dispatch, state -> store.dispatch(GlobalAction.GetMoonPayUserStatus) store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) } - is HomeAction.ReadCard -> { - if (!preferencesStorage.wasDisclaimerAccepted()) { - store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) - } else { - launchOnboardingService() - } - } + is HomeAction.ReadCard -> handleReadCard() is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) } next(action) @@ -46,39 +47,59 @@ private val homeMiddleware: Middleware = { dispatch, state -> } } -private fun launchOnboardingService() { - fun changeButtonState(state: ButtonState) { - val btnState = IndeterminateProgressButton(state) - store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) +private fun handleReadCard() { + if (!preferencesStorage.wasDisclaimerAccepted()) { + store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer)) + } else { + fun changeButtonState(state: ButtonState) { + val btnState = IndeterminateProgressButton(state) + store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) + } + + fun proceedToWalletScreen(tapWalletManager: TapWalletManager, scanResponse: ScanNoteResponse) { + changeButtonState(ButtonState.ENABLED) + scope.launch { + tapWalletManager.onCardScanned(scanResponse) + delay(DELAY_SDK_DIALOG_CLOSE) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } + } + changeButtonState(ButtonState.PROGRESS) + + store.dispatch(GlobalAction.ReadCard({ scanResponse -> + val tapWalletManager = store.state.globalState.tapWalletManager + if (!OnboardingHelper.isOnboardingCase(scanResponse)) { + proceedToWalletScreen(tapWalletManager, scanResponse) + return@ReadCard + } + + val service = OnboardingHelper.createService( + AppScreen.Home, + scanResponse, + tapWalletManager.walletManagerFactory + ) + if (service == null) { + proceedToWalletScreen(tapWalletManager, scanResponse) + return@ReadCard + } + + service.onInitializationProgress = { + when (it) { + ProgressState.Loading -> changeButtonState(ButtonState.PROGRESS) + ProgressState.Done -> changeButtonState(ButtonState.ENABLED) + ProgressState.Error -> changeButtonState(ButtonState.ENABLED) + } + } + service.onInitialized = { + store.dispatch(GlobalAction.Onboarding.Activate(service)) + store.dispatch(NavigationAction.NavigateTo(it, store.state.homeState.shareTransition)) + } + service.onError = { + store.dispatchErrorNotification(it) + } + post(DELAY_SDK_DIALOG_CLOSE) { service.initializeOnboarding() } + }, { + changeButtonState(ButtonState.ENABLED) + })) } - changeButtonState(ButtonState.PROGRESS) - - val walletManager = store.state.globalState.tapWalletManager - val service = OnboardingService( - walletManager, - preferencesStorage.usedCardsPrefStorage - ) - - service.onReadyToProceed = { - store.dispatch(OnboardingAction.SetData(it)) - changeButtonState(ButtonState.ENABLED) - val sharedTransition = store.state.homeState.shareTransition - store.dispatch(NavigationAction.NavigateTo(it.onboardingScreen, fragmentShareTransition = sharedTransition)) - } - - service.onFailedToProceedToOnboardingCase = { - changeButtonState(ButtonState.ENABLED) - walletManager.onCardScanned(it) - store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) - } - - service.onError = { - changeButtonState(ButtonState.ENABLED) - store.dispatch(GlobalAction.DebugShowError(TapError.CustomError(it))) - } - - store.dispatch(GlobalAction.ReadCard(service::onSuccess, { - changeButtonState(ButtonState.ENABLED) - store.dispatch(GlobalAction.DebugShowError(TapError.ScanCardError)) - })) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt new file mode 100644 index 0000000000..0b5b229bc4 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/AddressInfoBottomSheetDialog.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.features.onboarding + +import android.content.Context +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.common.extensions.copyToClipboard +import com.tangem.tap.common.extensions.dispatchDialogHide +import com.tangem.tap.common.redux.AppDialog +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.dialog_onboarding_address_info.* +import kotlinx.android.synthetic.main.layout_pseudo_toolbar.* + +/** +[REDACTED_AUTHOR] + */ +class AddressInfoBottomSheetDialog( + private val stateDialog: AppDialog.AddressInfoDialog, + context: Context +) : BottomSheetDialog(context) { + + init { + this.setContentView(R.layout.dialog_onboarding_address_info) + setOnCancelListener { store.dispatchDialogHide() } + } + + override fun show() { + super.show() + val data = stateDialog.addressData + + imv_close.setOnClickListener { + dismissWithAnimation = true + cancel() + } + imv_qr_code.setImageBitmap(data.qrCode) + tv_address.text = data.address + btn_fl_copy_address.setOnClickListener { + context.copyToClipboard(data.address) + stateDialog.onCopyAddress() + } + btn_fl_explore.setOnClickListener { + stateDialog.onExploreAddress() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt deleted file mode 100644 index 2fc0925f2e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingService.kt +++ /dev/null @@ -1,168 +0,0 @@ -package com.tangem.tap.features.onboarding - -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchain.common.WalletManagerFactory -import com.tangem.common.card.Card -import com.tangem.common.extensions.guard -import com.tangem.common.services.Result -import com.tangem.operations.attestation.OnlineCardVerifier -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.TapWalletManager -import com.tangem.tap.domain.TapWorkarounds.isTangemNote -import com.tangem.tap.domain.TapWorkarounds.isTangemWallet -import com.tangem.tap.domain.UrlBitmapLoader -import com.tangem.tap.domain.extensions.getArtworkUrl -import com.tangem.tap.domain.extensions.hasWallets -import com.tangem.tap.domain.extensions.makePrimaryWalletManager -import com.tangem.tap.domain.tasks.ScanNoteResponse -import com.tangem.tap.domain.twins.isTangemTwin -import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction -import com.tangem.tap.features.onboarding.redux.OnboardingData -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.persistence.UsedCardsPrefStorage -import com.tangem.tap.store -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -class OnboardingService( - private val tapWalletManager: TapWalletManager, - private val cardInfoStorage: UsedCardsPrefStorage -) { - - var onReadyToProceed: ((OnboardingData) -> Unit)? = null - set(value) { - field = value - onboardingDataLinker.onReady = { value?.invoke(it) } - } - var onFailedToProceedToOnboardingCase: (suspend (ScanNoteResponse) -> Unit)? = null - var onError: ((String) -> Unit)? = null - - private val onboardingDataLinker = OnboardingDataLinker() - - suspend fun onSuccess(response: ScanNoteResponse) { - onboardingDataLinker.scanNoteResponse = response - val card = response.card - val hasWallets = card.hasWallets() - - if (!card.isTangemNote() && !card.isTangemWallet() && !card.isTangemTwin()) { - if (hasWallets) { - cardInfoStorage.activate(card.cardId) - onFailedToProceedToOnboardingCase?.invoke(response) - return - } else { - cardInfoStorage.activate(card.cardId) // вызвать только после того, как был создан кошелек - onboardingDataLinker.navigateTo = AppScreen.OnboardingOther - } - } - if (cardInfoStorage.wasActivated(card.cardId)) { - onFailedToProceedToOnboardingCase?.invoke(response) - return - } - - loadCardImage(card) { onboardingDataLinker.cardArtwork = it } - - if (hasWallets) { - val walletManagerFactory = tapWalletManager.walletManagerFactory - when { - card.isTangemNote() -> loadBalanceNote(walletManagerFactory, response) - card.isTangemWallet() -> loadBalanceWallet(walletManagerFactory, response) - card.isTangemTwin() -> loadBalanceTwins(walletManagerFactory, response) - } - } else { - onboardingDataLinker.valueOfWalletAmount = BigDecimal.ZERO - } - - onboardingDataLinker.navigateTo = when { - card.isTangemNote() -> AppScreen.OnboardingNote - card.isTangemWallet() -> AppScreen.OnboardingWallet - card.isTangemTwin() -> AppScreen.OnboardingTwins - else -> AppScreen.OnboardingOther - } - } - - private suspend fun loadBalanceNote(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { - val walletManager: WalletManager = walletManagerFactory.makePrimaryWalletManager(response).guard { - onError?.invoke("Can't proceed: primary wallet manager didn't create") - return - } - walletManager.update() - store.dispatch(OnboardingNoteAction.SetWalletManager(walletManager)) - - val wallet = walletManager.wallet - val valueOfAmount = wallet.amounts[AmountType.Coin]?.value - if (valueOfAmount == null) { - onError?.invoke("Amount can't be NULL") - return - } - - onboardingDataLinker.valueOfWalletAmount = valueOfAmount - } - - private suspend fun loadBalanceWallet(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { - - } - - private suspend fun loadBalanceTwins(walletManagerFactory: WalletManagerFactory, response: ScanNoteResponse) { - - } - - private suspend fun loadCardImage(card: Card, callback: (Artwork?) -> Unit) { - fun loadBitmap(url: String, callback: (Artwork?) -> Unit) { - UrlBitmapLoader().loadBitmap(url) { - when (it) { - is Result.Success -> callback(Artwork(url, it.data)) - is Result.Failure -> callback(null) - } - } - } - - when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) { - is Result.Success -> { - val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL - loadBitmap(artworkUrl, callback) - } - is Result.Failure -> loadBitmap(Artwork.DEFAULT_IMG_URL, callback) - } - } - -} - -class OnboardingDataLinker { - var onReady: ((OnboardingData) -> Unit)? = null - - internal var scanNoteResponse: ScanNoteResponse? = null - set(value) { - field = value - tryToProceed() - } - internal var cardArtwork: Artwork? = null - set(value) { - field = value - tryToProceed() - } - internal var valueOfWalletAmount: BigDecimal? = null - set(value) { - field = value - tryToProceed() - } - internal var navigateTo: AppScreen? = null - set(value) { - field = value - tryToProceed() - } - - private fun tryToProceed() { - if (!isReady()) return - - val data = OnboardingData(scanNoteResponse!!, cardArtwork!!, valueOfWalletAmount, navigateTo!!) - onReady?.invoke(data) - } - - private fun isReady(): Boolean { - val objList = mutableListOf(scanNoteResponse, cardArtwork, valueOfWalletAmount, navigateTo) - return !objList.any { it == null } - } -} \ 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 new file mode 100644 index 0000000000..b27d17e06a --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/BaseOnboardingFragment.kt @@ -0,0 +1,80 @@ +package com.tangem.tap.features.onboarding.products + +import android.os.Bundle +import android.view.View +import androidx.transition.TransitionInflater +import com.tangem.tap.common.extensions.inflate +import com.tangem.tap.common.extensions.show +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.common.transitions.HomeToOnboardingTransition +import com.tangem.tap.features.send.BaseStoreFragment +import com.tangem.tap.store +import com.tangem.wallet.R +import kotlinx.android.synthetic.main.fragment_onboarding_main.* +import kotlinx.android.synthetic.main.layout_pseudo_toolbar.* +import kotlinx.android.synthetic.main.view_confetti.* +import org.rekotlin.StoreSubscriber + +/** +[REDACTED_AUTHOR] + */ +abstract class BaseOnboardingFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber { + + protected var fromScreen: AppScreen? = null + + override fun onCreate(savedInstanceState: Bundle?) { + fromScreen = store.state.globalState.onboardingService?.fromScreen + + super.onCreate(savedInstanceState) + } + + override fun configureTransitions() { + val inflater = TransitionInflater.from(requireContext()) + when (fromScreen) { + AppScreen.Home -> { + enterTransition = HomeToOnboardingTransition() + exitTransition = HomeToOnboardingTransition() + } + AppScreen.Wallet -> { + enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) + exitTransition = inflater.inflateTransition(android.R.transition.slide_top) + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + onboarding_top_container.inflate(getOnboardingTopContainerId(), true) + configureToolbar() + } + + private fun configureToolbar() { + when (fromScreen) { + AppScreen.Home -> { + toolbar.show(true) + pseudo_toolbar.visibility = View.GONE + } + AppScreen.Wallet -> { + toolbar.visibility = View.GONE + pseudo_toolbar.show(true) + imv_close.setOnClickListener { + store.dispatch(NavigationAction.PopBackTo()) + } + } + } + } + + abstract fun getOnboardingTopContainerId(): Int + + protected fun showConfetti(show: Boolean) { + lav_confetti.show(show) + + if (show) { + lav_confetti.playAnimation() + } else { + lav_confetti.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 index 730c8760fc..182f63d7bb 100644 --- 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 @@ -2,46 +2,46 @@ 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.TransitionInflater -import com.tangem.tap.common.extensions.addOnBackPressedDispatcher -import com.tangem.tap.common.extensions.beginDelayedTransition -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction -import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState -import com.tangem.tap.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep -import com.tangem.tap.features.send.BaseStoreFragment +import androidx.transition.TransitionManager +import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.common.extensions.getDrawableCompat +import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.postUi +import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget +import com.tangem.tap.common.transitions.InternalNoteLayoutTransition +import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment +import com.tangem.tap.features.onboarding.products.note.redux.* import com.tangem.tap.store import com.tangem.wallet.R -import kotlinx.android.synthetic.main.fragment_onboarding.* -import org.rekotlin.StoreSubscriber +import kotlinx.android.synthetic.main.layout_onboarding_bottom_action_views.* +import kotlinx.android.synthetic.main.layout_onboarding_note.* +import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* /** [REDACTED_AUTHOR] */ -class OnboardingNoteFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber { +class OnboardingNoteFragment : BaseOnboardingFragment() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget - activity?.addOnBackPressedDispatcher { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingNoteStep.None)) - } - - val inflater = TransitionInflater.from(requireContext()) - enterTransition = inflater.inflateTransition(R.transition.fade) - exitTransition = inflater.inflateTransition(R.transition.fade) - } + override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - store.dispatch(OnboardingNoteAction.Init) + btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container) + val resources = AndroidResources( + OnboardingStringResources( + R.string.copy_toast_msg + ) + ) - imv_front_card.setImageBitmap(store.state.onboardingState.onboardingData?.cardArtwork?.artwork) + store.dispatch(OnboardingNoteAction.SetResources(resources)) + store.dispatch(OnboardingNoteAction.DetermineStepOfScreen) } override fun subscribeToStore() { @@ -55,79 +55,107 @@ class OnboardingNoteFragment : BaseStoreFragment(R.layout.fragment_onboarding), override fun newState(state: OnboardingNoteState) { if (activity == null) return + state.artworkBitmap?.let { imv_front_card.setImageBitmap(it) } pb_state.max = state.steps.size - 1 + pb_state.progress = state.progress - onboarding_main_container.beginDelayedTransition() when (state.currentStep) { OnboardingNoteStep.None -> setupNoneState(state) OnboardingNoteStep.CreateWallet -> setupCreateWalletState(state) OnboardingNoteStep.TopUpWallet -> setupTopUpWalletState(state) - OnboardingNoteStep.Done -> setupSuccessState(state) + OnboardingNoteStep.Done -> setupDoneState(state) } + setBalance(state) + showConfetti(state.showConfetti) + } + + private fun setBalance(state: OnboardingNoteState) { + if (state.balanceCurrency.blockchain == Blockchain.Unknown) return + + val balanceValue = state.balanceValue.stripZeroPlainString() + val currency = state.balanceCurrency.currencySymbol + tv_balance_value.text = "$balanceValue $currency" } private fun setupNoneState(state: OnboardingNoteState) { - pb_state.progress = state.progress + btn_main_action.isVisible = false + btn_alternative_action.isVisible = false + btnRefreshBalanceWidget.show(false) + tv_header.isVisible = false + tv_body.isVisible = false - btn_main_action.isVisible = true - btn_alternative_action.isVisible = true - btn_check_balance.isVisible = true - tv_header.isVisible = true - tv_body.isVisible = true - - btn_main_action.text = "NULL" - btn_alternative_action.text = "NULL" - btn_check_balance.text = "NULL balance" - tv_header.text = "NULL" - tv_body.text = "NULL" + btn_main_action.text = "" + btn_alternative_action.text = "" + tv_header.text = "" + tv_body.text = "" } private fun setupCreateWalletState(state: OnboardingNoteState) { - pb_state.progress = state.progress - btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet) btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.CreateWallet) } - btn_alternative_action.setText(R.string.onboarding_button_what_does_it_mean) btn_alternative_action.setOnClickListener { } - btn_check_balance.isVisible = false - btn_check_balance.setOnClickListener { } + btnRefreshBalanceWidget.mainView.setOnClickListener(null) tv_header.setText(R.string.onboarding_create_wallet_header) tv_body.setText(R.string.onboarding_create_wallet_body) + + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_circle)) + updateConstraints(R.layout.lp_onboarding_create_wallet) } private fun setupTopUpWalletState(state: OnboardingNoteState) { - pb_state.progress = state.progress - btn_main_action.setText(R.string.onboarding_top_up_button_but_crypto) - btn_main_action.setOnClickListener { store.dispatch(OnboardingNoteAction.TopUp) } + btn_main_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.TopUp) + } btn_alternative_action.setText(R.string.onboarding_top_up_button_show_wallet_address) - btn_alternative_action.setOnClickListener { } - - btn_check_balance.isVisible = false - btn_check_balance.setOnClickListener { } + btn_alternative_action.setOnClickListener { + store.dispatch(OnboardingNoteAction.ShowAddressInfoDialog) + } tv_header.setText(R.string.onboarding_top_up_header) tv_body.setText(R.string.onboarding_top_up_body) + + btnRefreshBalanceWidget.changeState(state.balanceState) + if (btnRefreshBalanceWidget.isShowing != true) { + postUi(300) { + btnRefreshBalanceWidget.mainView.setOnClickListener { + store.dispatch(OnboardingNoteAction.Balance.Update) + } + } + } + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) + updateConstraints(R.layout.lp_onboarding_topup_wallet) } - private fun setupSuccessState(state: OnboardingNoteState) { - pb_state.progress = state.progress - + private fun setupDoneState(state: OnboardingNoteState) { btn_main_action.setText(R.string.onboarding_done_button_continue) - btn_main_action.setOnClickListener { store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } + btn_main_action.setOnClickListener { + showConfetti(false) + store.dispatch(OnboardingNoteAction.Done) + } btn_alternative_action.isVisible = false btn_alternative_action.setText("") btn_alternative_action.setOnClickListener { } - - btn_check_balance.isVisible = false - btn_check_balance.setOnClickListener { } + btnRefreshBalanceWidget.mainView.setOnClickListener(null) tv_header.setText(R.string.onboarding_done_header) tv_body.setText(R.string.onboarding_done_body) + + imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) + updateConstraints(R.layout.lp_onboarding_done) + } + + private fun updateConstraints(@LayoutRes layoutId: Int) { + val constraintSet = ConstraintSet() + constraintSet.clone(requireContext(), layoutId) + constraintSet.applyTo(onboarding_main_container) + val transition = InternalNoteLayoutTransition() + transition.interpolator = OvershootInterpolator() + TransitionManager.beginDelayedTransition(onboarding_main_container, transition) } } \ 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 index bab556d68b..9c1644dda1 100644 --- 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 @@ -1,16 +1,31 @@ package com.tangem.tap.features.onboarding.products.note.redux -import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.features.onboarding.service.OnboardingWalletBalance import org.rekotlin.Action /** [REDACTED_AUTHOR] */ sealed class OnboardingNoteAction : Action { - object Init : OnboardingNoteAction() + // from user, ui + data class SetResources(val resources: AndroidResources) : OnboardingNoteAction() object CreateWallet : OnboardingNoteAction() object TopUp : OnboardingNoteAction() + object ShowAddressInfoDialog : OnboardingNoteAction() + + // from redux + object DetermineStepOfScreen : OnboardingNoteAction() object Done : OnboardingNoteAction() - data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction() + data class SetStepOfScreen(val step: OnboardingNoteStep) : OnboardingNoteAction() + + sealed class Balance { + object Update : OnboardingNoteAction() + data class Set(val balance: OnboardingWalletBalance) : 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 index 7f5ee16c3f..2eab43bc1b 100644 --- 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 @@ -1,9 +1,15 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.common.CompletionResult +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.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.onboarding.service.balanceIsToppedUp +import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager @@ -13,8 +19,6 @@ import org.rekotlin.Middleware class OnboardingNoteMiddleware { companion object { - private const val CARD_SHOP_URI = - "https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate" val handler = onboardingNoteMiddleware } } @@ -22,51 +26,93 @@ class OnboardingNoteMiddleware { private val onboardingNoteMiddleware: Middleware = { dispatch, state -> { next -> { action -> - handleMainOnboardingAction(action, ::handleNoteAction) + handleNoteAction(action) next(action) } } } -private fun handleMainOnboardingAction(action: Action, isNotMainAction: (Action) -> Unit) { - val action = action as? OnboardingAction ?: isNotMainAction(action) - - when (action) { - } -} - private fun handleNoteAction(action: Action) { - val onboardingState = store.state.onboardingState + if (action !is OnboardingNoteAction) return + val service = store.state.onboardingNoteState.onboardingService ?: return val noteState = store.state.onboardingNoteState when (action) { - is OnboardingNoteAction.SetWalletManager -> { - } - is OnboardingNoteAction.Init -> { - val step = if (noteState.walletManager == null) { - OnboardingNoteStep.CreateWallet - } else { - OnboardingNoteStep.TopUpWallet + is OnboardingNoteAction.DetermineStepOfScreen -> { + val step = when { + service.walletManager == null -> OnboardingNoteStep.CreateWallet + service.balanceIsToppedUp() -> OnboardingNoteStep.Done + else -> OnboardingNoteStep.TopUpWallet + } + store.dispatch((OnboardingNoteAction.SetStepOfScreen(step))) + } + is OnboardingNoteAction.SetStepOfScreen -> { + if (action.step == OnboardingNoteStep.Done) { + service.activationFinished() + postUi(500) { store.dispatch(OnboardingNoteAction.Confetti.Show) } } - store.dispatch(OnboardingAction.SetInitialStepOfScreen(step)) } is OnboardingNoteAction.CreateWallet -> { scope.launch { - val scanNoteResponse = onboardingState.onboardingData?.scanNoteResponse - val result = tangemSdkManager.createWallet(scanNoteResponse?.card?.cardId) - when (result) { - is CompletionResult.Success -> { -// val scanNoteResponse = scanNoteResponse!!.copy(card = result.data) -// store.dispatch(OnboardingAction.SetScanNoteResponse(scanNoteResponse)) - } - is CompletionResult.Failure -> { - store.dispatch(OnboardingAction.Error("Failed to create wallet")) + val result = tangemSdkManager.createProductWallet(service.scanResponse.card.cardId) + withMainContext { + when (result) { + is CompletionResult.Success -> { + val updatedResponse = service.scanResponse.copy(card = result.data) + service.scanResponse = updatedResponse + service.activationStarted() + + store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet)) + store.dispatch(OnboardingNoteAction.Balance.Update) + } + is CompletionResult.Failure -> { +// do nothing + } } } } } - is OnboardingNoteAction.TopUp -> { + is OnboardingNoteAction.Balance.Update -> { + val loadingBalance = service.getBalance().copy( + state = ProgressState.Loading, + error = null, + criticalError = null + ) + store.dispatch(OnboardingNoteAction.Balance.Set(loadingBalance)) + scope.launch { + val loadedBalance = service.updateBalance() + if (loadedBalance.criticalError != null) { + store.dispatchErrorNotification(loadedBalance.criticalError) + } + withMainContext { store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) } + } + } + is OnboardingNoteAction.Balance.Set -> { + if (service.balanceIsToppedUp()) { + store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done)) + } + } + is OnboardingNoteAction.ShowAddressInfoDialog -> { + val addressData = noteState.onboardingService?.getAddressData() ?: return + val addressWasCopied = noteState.resources.strings.addressWasCopied + val appDialog = AppDialog.AddressInfoDialog( + addressData, + onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, + onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } + ) + store.dispatchDialogShow(appDialog) + } + is OnboardingNoteAction.TopUp -> { + val topUpUrl = noteState.onboardingService?.getToUpUrl() ?: return + store.dispatchOpenUrl(topUpUrl) + } + OnboardingNoteAction.Done -> { + store.dispatch(GlobalAction.Onboarding.Deactivate) + scope.launch { + store.state.globalState.tapWalletManager.onCardScanned(service.scanResponse) + store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) + } } } } \ 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 index b6a50be846..efe2540b66 100644 --- 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 @@ -1,8 +1,8 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.tap.common.redux.AppState -import com.tangem.tap.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.onboarding.service.OnboardingNoteService import org.rekotlin.Action class OnboardingNoteReducer { @@ -15,23 +15,46 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt var state = appState.onboardingNoteState when (action) { - is OnboardingAction.SetInitialStepOfScreen -> { - (action.step as? OnboardingNoteStep)?.let { - if (it != state.currentStep && state.steps.contains(it)) { - state = state.copy(currentStep = it) - } + is GlobalAction.Onboarding.Activate -> { + if (action.onboardingService is OnboardingNoteService) { + val service = action.onboardingService + val balance = service.getBalance() + state = state.copy( + onboardingService = service, + artworkBitmap = service.getArtwork().value?.artwork, + balanceValue = balance.value, + balanceCurrency = balance.currency, + balanceState = balance.state, + amountToCreateAccount = balance.amountToCreateAccount, + showConfetti = false + ) } } - OnboardingAction.SwitchToNextStep -> { - val progress = state.progress - if (progress >= 0 && progress != state.steps.size - 1) { - state = state.copy(currentStep = state.steps[progress + 1]) + GlobalAction.Onboarding.Deactivate -> { + state = OnboardingNoteState() + } + is OnboardingNoteAction.SetResources -> { + state = state.copy(resources = action.resources) + } + is OnboardingNoteAction.Balance.Set -> { + state = state.copy( + balanceValue = action.balance.value, + balanceCurrency = action.balance.currency, + balanceState = action.balance.state, + amountToCreateAccount = action.balance.amountToCreateAccount, + ) + } + is OnboardingNoteAction.SetStepOfScreen -> { + if (action.step != state.currentStep && state.steps.contains(action.step)) { + state = state.copy(currentStep = action.step) } } - is OnboardingNoteAction.SetWalletManager -> { - state = state.copy(walletManager = action.walletManager) + is OnboardingNoteAction.Confetti.Show -> { + state = state.copy(showConfetti = true) + } + is OnboardingNoteAction.Confetti.Hide -> { + state = state.copy(showConfetti = false) } - } return state 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 index ae5677779f..b95c58510d 100644 --- 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 @@ -1,18 +1,42 @@ package com.tangem.tap.features.onboarding.products.note.redux -import com.tangem.blockchain.common.WalletManager -import com.tangem.tap.features.onboarding.redux.OnboardingNoteStep +import android.graphics.Bitmap +import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.features.onboarding.service.OnboardingNoteService +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.ProgressState import org.rekotlin.StateType +import java.math.BigDecimal /** [REDACTED_AUTHOR] */ data class OnboardingNoteState( - val walletManager: WalletManager? = null, + val onboardingService: OnboardingNoteService? = null, + val resources: AndroidResources = AndroidResources(), + // UI + val showConfetti: Boolean = false, + val artworkBitmap: Bitmap? = null, + val balanceValue: BigDecimal = BigDecimal.ZERO, + val balanceCurrency: Currency = Currency.Blockchain(Blockchain.Unknown), + val balanceState: ProgressState = ProgressState.Done, + val amountToCreateAccount: String? = null, val currentStep: OnboardingNoteStep = OnboardingNoteStep.None, val steps: List = OnboardingNoteStep.values().toList(), ) : StateType { val progress: Int get() = steps.indexOf(currentStep) -} \ No newline at end of file +} + +enum class OnboardingNoteStep { + None, CreateWallet, TopUpWallet, Done +} + +data class AndroidResources( + val strings: OnboardingStringResources = OnboardingStringResources() +) + +data class OnboardingStringResources( + val addressWasCopied: Int = -1 +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt similarity index 62% rename from app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt rename to app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index cada557578..b0de1e9949 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/old/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -1,31 +1,20 @@ -package com.tangem.tap.features.onboarding.products.old +package com.tangem.tap.features.onboarding.products.otherCards import android.os.Bundle import android.view.View import androidx.transition.TransitionInflater -import com.tangem.tap.common.extensions.addOnBackPressedDispatcher -import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingOtherCardsState -import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep import com.tangem.tap.features.send.BaseStoreFragment -import com.tangem.tap.store import com.tangem.wallet.R import org.rekotlin.StoreSubscriber /** [REDACTED_AUTHOR] */ -class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding), StoreSubscriber { +class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboarding_main), StoreSubscriber { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - activity?.addOnBackPressedDispatcher { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done)) - } - val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.fade) exitTransition = inflater.inflateTransition(R.transition.fade) @@ -47,4 +36,6 @@ class OnboardingOtherCardsFragment : BaseStoreFragment(R.layout.fragment_onboard if (activity == null) return } -} \ No newline at end of file +} + +class OnboardingOtherCardsState {} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt index 5b2eba2423..c947f32ab9 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/OnboardingWalletFragment.kt @@ -3,11 +3,7 @@ package com.tangem.tap.features.onboarding.products.wallet import android.os.Bundle import android.view.View import androidx.transition.TransitionInflater -import com.tangem.tap.common.extensions.addOnBackPressedDispatcher -import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletState -import com.tangem.tap.features.onboarding.redux.OnboardingAction -import com.tangem.tap.features.onboarding.redux.OnboardingOtherStep import com.tangem.tap.features.send.BaseStoreFragment import com.tangem.tap.store import com.tangem.wallet.R @@ -21,11 +17,6 @@ class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - activity?.addOnBackPressedDispatcher { - store.dispatch(NavigationAction.PopBackTo()) - store.dispatch(OnboardingAction.SetInitialStepOfScreen(OnboardingOtherStep.Done)) - } - val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.fade) exitTransition = inflater.inflateTransition(R.transition.fade) @@ -33,8 +24,6 @@ class OnboardingWalletFragment : BaseStoreFragment(R.layout.fragment_onboarding_ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - store.dispatch(OnboardingAction.OnboardingOtherAction.Init) } override fun subscribeToStore() { 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 index 9b8579171a..5207a85b3d 100644 --- 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 @@ -1,6 +1,5 @@ package com.tangem.tap.features.onboarding.products.wallet.redux -import com.tangem.tap.features.onboarding.redux.OnboardingStep import org.rekotlin.StateType /** @@ -10,6 +9,6 @@ data class OnboardingWalletState( val any: String? = null ) : StateType -enum class OnboardingWalletStep : OnboardingStep { +enum class OnboardingWalletStep { None, CreateWallet, TopUpWallet, Backup, Done } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt deleted file mode 100644 index 7a904b47ee..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingAction.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import org.rekotlin.Action - -/** -[REDACTED_AUTHOR] - */ -sealed class OnboardingAction : Action { - - data class SetInitialStepOfScreen(val step: OnboardingStep) : OnboardingAction() - object SwitchToNextStep : OnboardingAction() - - data class SetData(val data: OnboardingData) : OnboardingAction() - - sealed class OnboardingOtherAction : Action { - object Init : OnboardingOtherAction() - object CreateWallet : OnboardingOtherAction() - object Done : OnboardingOtherAction() - } - - class Error(val message: String) : OnboardingAction() -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt deleted file mode 100644 index ea373a5f41..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingMiddleware.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import com.tangem.tap.common.redux.AppState -import com.tangem.tap.store -import org.rekotlin.Middleware - -class OnboardingMiddleware { - companion object { - val handler = onboardingMiddleware - } -} - -private val onboardingMiddleware: Middleware = { dispatch, state -> - { next -> - { action -> - val onboardingState = store.state.onboardingState - when (action) { - } - next(action) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt deleted file mode 100644 index 5ee024922d..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingReducer.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import com.tangem.tap.common.redux.AppState -import org.rekotlin.Action - -class OnboardingReducer { - companion object { - fun reduce(action: Action, state: AppState): OnboardingState = internalReduce(action, state) - } -} - -private fun internalReduce(action: Action, appState: AppState): OnboardingState { - - if (action !is OnboardingAction) return appState.onboardingState - - var state = appState.onboardingState - when (action) { - is OnboardingAction.SetData -> { - state = state.copy(onboardingData = action.data) - } - } - - return state -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt deleted file mode 100644 index 2a4909de43..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/OnboardingState.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import org.rekotlin.StateType - -data class OnboardingState( - val onboardingData: OnboardingData? = null, -) : StateType - -data class OnboardingOtherCardsState( - val any: String? = null -) : StateType \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt b/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt deleted file mode 100644 index cc422410a5..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/redux/StateSupportClasses.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.tap.features.onboarding.redux - -import com.tangem.tap.common.redux.navigation.AppScreen -import com.tangem.tap.domain.tasks.ScanNoteResponse -import com.tangem.tap.features.wallet.redux.Artwork -import java.math.BigDecimal - -/** -[REDACTED_AUTHOR] - */ -data class OnboardingData( - val scanNoteResponse: ScanNoteResponse, - val cardArtwork: Artwork, - val balance: BigDecimal?, - val onboardingScreen: AppScreen -) - -interface OnboardingStep - -enum class OnboardingNoteStep : OnboardingStep { - None, CreateWallet, TopUpWallet, Done -} - -enum class OnboardingOtherStep : OnboardingStep { - CreateWallet, Done -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt new file mode 100644 index 0000000000..7cf88da9c5 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.features.onboarding.service + +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.TapWorkarounds.isTangemNote +import com.tangem.tap.domain.extensions.hasWallets +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.preferencesStorage + +/** +[REDACTED_AUTHOR] + */ +class OnboardingHelper { + companion object { + + fun isOnboardingCase(response: ScanNoteResponse): Boolean = if (response.card.hasWallets()) + preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true + + fun createService( + fromScreen: AppScreen, + scanResponse: ScanNoteResponse, + walletManagerFactory: WalletManagerFactory, + ): ProductOnboardingService? { + val card = scanResponse.card + val cardInfoStorage = preferencesStorage.usedCardsPrefStorage + return when { + card.isTangemNote() -> OnboardingNoteService(fromScreen, scanResponse, cardInfoStorage, + walletManagerFactory) + else -> null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt new file mode 100644 index 0000000000..4404a8e35b --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt @@ -0,0 +1,21 @@ +package com.tangem.tap.features.onboarding.service + +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.persistence.UsedCardsPrefStorage + +/** +[REDACTED_AUTHOR] + */ +class OnboardingNoteService( + override val fromScreen: AppScreen, + override var scanResponse: ScanNoteResponse, + cardInfoStorage: UsedCardsPrefStorage, + walletManagerFactory: WalletManagerFactory, +) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) { + + override fun navigateToScreen(): AppScreen { + return AppScreen.OnboardingNote + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt new file mode 100644 index 0000000000..44c4c99980 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt @@ -0,0 +1,295 @@ +package com.tangem.tap.features.onboarding.service + +import android.util.Log +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.common.card.Card +import com.tangem.common.extensions.guard +import com.tangem.common.extensions.isZero +import com.tangem.common.services.Result +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification +import com.tangem.tap.common.extensions.isPositive +import com.tangem.tap.common.extensions.safeUpdate +import com.tangem.tap.common.extensions.withMainContext +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.UrlBitmapLoader +import com.tangem.tap.domain.extensions.getArtworkUrl +import com.tangem.tap.domain.extensions.makePrimaryWalletManager +import com.tangem.tap.domain.tasks.ScanNoteResponse +import com.tangem.tap.domain.topup.TradeCryptoHelper +import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.features.wallet.redux.Artwork +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.features.wallet.redux.ProgressState +import com.tangem.tap.features.wallet.redux.reducers.createAddressesData +import com.tangem.tap.persistence.UsedCardsPrefStorage +import com.tangem.tap.store +import kotlinx.coroutines.* +import java.io.PrintWriter +import java.io.StringWriter +import java.math.BigDecimal +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** +[REDACTED_AUTHOR] + */ +interface OnboardingService { + val fromScreen: AppScreen + val scanResponse: ScanNoteResponse + + // val cardInfoStorage: UsedCardsPrefStorage + var walletManager: WalletManager? + + var onInitializationProgress: ((ProgressState) -> Unit)? + var onInitialized: ((AppScreen) -> Unit)? + var onError: ((TapError) -> Unit)? + + fun initializeOnboarding() + fun getArtwork(): OnboardingArtwork + fun getBalance(): OnboardingWalletBalance + suspend fun updateBalance(): OnboardingWalletBalance + fun getToUpUrl(): String? + fun getAddressData(): AddressData? + + fun activationStarted() + fun activationFinished() +} + +abstract class ProductOnboardingService( + private val cardInfoStorage: UsedCardsPrefStorage, + private val walletManagerFactory: WalletManagerFactory, +) : OnboardingService { + + override var walletManager: WalletManager? = null + + override var onInitializationProgress: ((ProgressState) -> Unit)? = null + override var onInitialized: ((AppScreen) -> Unit)? = null + override var onError: ((TapError) -> Unit)? = null + + protected var isProceeded = false + + protected var loadedCardArtwork: OnboardingArtwork = OnboardingArtwork.loading() + set(value) { + field = value + tryToProceed() + } + + protected var loadedBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading() + set(value) { + field = value + tryToProceed() + } + + protected val internalScope = CoroutineScope(Job() + Dispatchers.IO + coroutineExceptionHandler()) + + override fun initializeOnboarding() { + val card = scanResponse.card + onInitializationProgress?.invoke(ProgressState.Loading) + + internalScope.launch { + val artwork = loadCardArtwork(card) + withMainContext { + loadedCardArtwork = if (artwork == null) OnboardingArtwork.error() else OnboardingArtwork.done(artwork) + } + } + internalScope.launch { updateBalance() } + } + + override fun getArtwork(): OnboardingArtwork = loadedCardArtwork + + override fun getBalance(): OnboardingWalletBalance = loadedBalance + + override fun getToUpUrl(): String? { + val wallet = walletManager?.wallet ?: return null + val config = store.state.globalState.configManager?.config ?: return null + val defaultAddress = wallet.address + + return TradeCryptoHelper.getUrl( + TradeCryptoHelper.Action.Buy, + wallet.blockchain, + wallet.blockchain.currency, + defaultAddress, + config.moonPayApiKey, + config.moonPayApiSecretKey + ) + } + + override fun getAddressData(): AddressData? { + val wallet = walletManager?.wallet ?: return null + + val addressDataList = wallet.createAddressesData() + return if (addressDataList.isEmpty()) null + else addressDataList[0] + } + + protected suspend fun loadCardArtwork(card: Card): Artwork? { + return when (val cardInfoResult = OnlineCardVerifier().getCardInfo(card.cardId, card.cardPublicKey)) { + is Result.Success -> { + val artworkUrl = card.getArtworkUrl(cardInfoResult.data.artwork?.id) ?: Artwork.DEFAULT_IMG_URL + loadBitmap(artworkUrl) + } + is Result.Failure -> { + loadBitmap(Artwork.DEFAULT_IMG_URL) + } + } + } + + override suspend fun updateBalance(): OnboardingWalletBalance { + withMainContext { loadedBalance = OnboardingWalletBalance.loading(loadedBalance.value) } + + val walletManager = walletManager ?: walletManagerFactory.makePrimaryWalletManager(scanResponse).guard { + val customError = TapError.CustomError("Loading cancelled. Cause: wallet manager didn't created") + loadedBalance = OnboardingWalletBalance.error(customError) + return loadedBalance + } + + this.walletManager = walletManager + val currency = Currency.Blockchain(walletManager.wallet.blockchain) + val updatedBalance = when (val result = walletManager.safeUpdate()) { + 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, hasForIncomingTransactions(), currency) + } else { + OnboardingWalletBalance.done(valueOfAmount, false, currency) + } + balance + } + } + is Result.Failure -> { + val error = (result.error as? TapError) ?: TapError.UnknownError + when (error) { + is TapError.WalletManagerUpdate.NoAccountError -> OnboardingWalletBalance.error(error) +// NoInternetConnection, WalletManagerUpdate.InternalError + else -> OnboardingWalletBalance.criticalError(error) + } + } + } + withMainContext { loadedBalance = updatedBalance } + return loadedBalance + } + + override fun activationStarted() { + cardInfoStorage.activationStarted(scanResponse.card.cardId) + + } + + override fun activationFinished() { + cardInfoStorage.activationFinished(scanResponse.card.cardId) + } + + protected open suspend fun hasForIncomingTransactions(): Boolean { + return false + } + + protected open fun tryToProceed() { + if (isProceeded) return + + loadedBalance.criticalError?.let { + sendError(it) + return + } + if (!isReadyToProceed()) return + + isProceeded = true + onInitializationProgress?.invoke(ProgressState.Done) + onInitialized?.invoke(navigateToScreen()) + } + + protected open fun isReadyToProceed(): Boolean { + return loadedBalance.state != ProgressState.Loading && loadedCardArtwork.state != ProgressState.Loading + } + + protected fun sendError(error: TapError) { + onInitializationProgress?.invoke(ProgressState.Error) + onError?.invoke(error) + } + + protected abstract fun navigateToScreen(): AppScreen + + private suspend fun loadBitmap(url: String): Artwork? { + return withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + UrlBitmapLoader().loadBitmap(url) { + val result = when (it) { + is Result.Success -> Artwork(url, it.data) + is Result.Failure -> null + } + continuation.resume(result) + } + } + } + } + + private fun coroutineExceptionHandler(): CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> + StringWriter().apply { + throwable.printStackTrace(PrintWriter(this)) + Log.e("Coroutine", this.toString()) + store.dispatchDebugErrorNotification(this.toString()) + } + } +} + +data class OnboardingArtwork( + val value: Artwork? = null, + val state: ProgressState +) { + companion object { + // balance was not loaded at all when any error occurred + fun error(): OnboardingArtwork = OnboardingArtwork(state = ProgressState.Error) + + // balance is loading + fun loading(): OnboardingArtwork = OnboardingArtwork(state = ProgressState.Loading) + + // balance was loaded + fun done(artwork: Artwork): OnboardingArtwork = OnboardingArtwork(artwork, ProgressState.Done) + } +} + +data class OnboardingWalletBalance( + val value: BigDecimal = BigDecimal.ZERO, + val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown), + val hasIncomingTransaction: Boolean = false, + val state: ProgressState, + val error: TapError? = null, + val criticalError: TapError? = null, +) { + + val amountToCreateAccount: String? + get() = if (error is TapError.WalletManagerUpdate.NoAccountError) error.customMessage else null + + 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, currency: Currency.Blockchain): OnboardingWalletBalance = + OnboardingWalletBalance(value, currency, hasTransactions, ProgressState.Done) + } +} + +fun ProductOnboardingService.balanceIsToppedUp(): Boolean { + val balance = getBalance() + return balance.value.isPositive() || balance.hasIncomingTransaction +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt index 9d19112f8a..75a2a5ad80 100644 --- a/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/BaseStoreFragment.kt @@ -24,7 +24,10 @@ abstract class BaseStoreFragment(layoutId: Int) : Fragment(layoutId) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + configureTransitions() + } + protected open fun configureTransitions() { val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.slide_right) exitTransition = inflater.inflateTransition(R.transition.fade)