diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index dbef280f6f..41b89b88e3 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -75,7 +75,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler { val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 val isScannedBefore = store.state.globalState.scanResponse != null - val isOnboardingServiceActive = store.state.globalState.onboardingService != null + val isOnboardingServiceActive = store.state.globalState.onboardingManager != null if (backStackIsEmpty || (!isOnboardingServiceActive && !isScannedBefore)) { store.dispatch(NavigationAction.NavigateTo(AppScreen.Home)) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt b/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt new file mode 100644 index 0000000000..69fbba97b6 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/extensions/Transition.kt @@ -0,0 +1,24 @@ +package com.tangem.tap.common.extensions + +import androidx.transition.Transition + +/** +[REDACTED_AUTHOR] + */ +inline fun Transition.addListener( + crossinline onStart: (animator: Transition) -> Unit = {}, + crossinline onEnd: (animator: Transition) -> Unit = {}, + crossinline onCancel: (animator: Transition) -> Unit = {}, + crossinline onPause: (animator: Transition) -> Unit = {}, + crossinline onRepeat: (animator: Transition) -> Unit = {} +): Transition.TransitionListener { + val listener = object : Transition.TransitionListener { + override fun onTransitionStart(transition: Transition) = onStart(transition) + override fun onTransitionEnd(transition: Transition) = onEnd(transition) + override fun onTransitionCancel(transition: Transition) = onCancel(transition) + override fun onTransitionPause(transition: Transition) = onPause(transition) + override fun onTransitionResume(transition: Transition) = onRepeat(transition) + } + addListener(listener) + return listener +} \ No newline at end of file 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 683f1574a3..8f7402ff1f 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 @@ -7,7 +7,11 @@ import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.amountToCreateAccount import com.tangem.tap.domain.extensions.isNoAccountError import com.tangem.tap.domain.getFirstToken +import com.tangem.tap.domain.topup.TradeCryptoHelper +import com.tangem.tap.features.wallet.redux.AddressData +import com.tangem.tap.features.wallet.redux.reducers.createAddressesData import com.tangem.tap.network.NetworkConnectivity +import com.tangem.tap.store /** [REDACTED_AUTHOR] @@ -29,4 +33,27 @@ suspend fun WalletManager.safeUpdate(): Result = try { Result.Failure(TapError.WalletManagerUpdate.InternalError(message)) } } +} + +fun WalletManager?.getToUpUrl(): String? { + val wallet = this?.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 + ) +} + +fun WalletManager?.getAddressData(): AddressData? { + val wallet = this?.wallet ?: return null + + val addressDataList = wallet.createAddressesData() + return if (addressDataList.isEmpty()) null + else addressDataList[0] } \ 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 50efb26994..b29536f6d3 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 @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError import com.tangem.tap.common.redux.* +import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessage @@ -12,7 +13,6 @@ import com.tangem.tap.domain.tasks.product.ScanResponse 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 @@ -29,11 +29,11 @@ sealed class GlobalAction : Action { object HideDialog : GlobalAction() sealed class Onboarding { - data class Activate(val onboardingService: ProductOnboardingService) : GlobalAction() - object Deactivate : GlobalAction() + data class Start(val scanResponse: ScanResponse, val fromScreen: AppScreen) : GlobalAction() + object Stop : GlobalAction() } - data class ReadCard( + data class ScanCard( val onSuccess: ((ScanResponse) -> Unit)? = null, val onFailure: ((TangemError) -> Unit)? = null, val messageResId: Int? = null, 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 2f8cbfffc4..ea80ab463c 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 @@ -87,7 +87,7 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState } } } - is GlobalAction.ReadCard -> { + is GlobalAction.ScanCard -> { scope.launch { val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId) store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) 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 30eabac6e4..0d1894f736 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -1,6 +1,8 @@ package com.tangem.tap.common.redux.global import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.onboarding.OnboardingManager +import com.tangem.tap.preferencesStorage import org.rekotlin.Action fun globalReducer(action: Action, state: AppState): GlobalState { @@ -10,11 +12,13 @@ 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.Start -> { + val usedCardsPrefStorage = preferencesStorage.usedCardsPrefStorage + val onboardingState = OnboardingManager(action.scanResponse, action.fromScreen, usedCardsPrefStorage) + globalState.copy(onboardingManager = onboardingState) } - is GlobalAction.Onboarding.Deactivate -> { - globalState.copy(onboardingService = null) + is GlobalAction.Onboarding.Stop -> { + globalState.copy(onboardingManager = null) } is GlobalAction.ScanFailsCounter.Increment -> { globalState.copy(scanCardFailsCounter = globalState.scanCardFailsCounter + 1) @@ -36,11 +40,11 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager) is GlobalAction.UpdateWalletSignedHashes -> { val wallet = globalState.scanResponse?.card - ?.wallet(action.walletPublicKey) - ?.copy( - totalSignedHashes = action.walletSignedHashes, - remainingSignatures = action.remainingSignatures - ) + ?.wallet(action.walletPublicKey) + ?.copy( + totalSignedHashes = action.walletSignedHashes, + remainingSignatures = action.remainingSignatures + ) val card = globalState.scanResponse?.card wallet?.let { globalState.scanResponse.card.updateWallet(wallet) } 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 94eb578fc4..e27641b854 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,13 +8,14 @@ import com.tangem.tap.domain.configurable.config.ConfigManager import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.feedback.FeedbackManager -import com.tangem.tap.features.onboarding.service.ProductOnboardingService +import com.tangem.tap.features.onboarding.OnboardingManager import com.tangem.tap.network.coinmarketcap.CoinMarketCapService import com.tangem.tap.network.moonpay.MoonPayUserStatus import org.rekotlin.StateType data class GlobalState( val scanResponse: ScanResponse? = null, + val onboardingManager: OnboardingManager? = null, val cardVerifiedOnline: Boolean = false, val tapWalletManager: TapWalletManager = TapWalletManager(), val payIdManager: PayIdManager = PayIdManager(), @@ -26,7 +27,6 @@ data class GlobalState( val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, val moonPayUserStatus: MoonPayUserStatus? = null, - val onboardingService: ProductOnboardingService? = null, ) : StateType typealias CryptoCurrencyName = String 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 76a9bc46f1..32403ae363 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -17,7 +17,6 @@ import com.tangem.tap.domain.extensions.* import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.domain.tokens.CardCurrencies import com.tangem.tap.domain.twins.getTwinCardNumber -import com.tangem.tap.domain.twins.getTwinsCardId import com.tangem.tap.domain.twins.isTangemTwin import com.tangem.tap.features.tokens.redux.TokensAction import com.tangem.tap.features.wallet.redux.Currency @@ -91,12 +90,8 @@ class TapWalletManager { store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard)) store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed)) if (data.card.isTangemTwin()) { - val secondCardId = data.card.getTwinsCardId() - val cardNumber = data.card.getTwinCardNumber() - if (secondCardId != null && cardNumber != null) { - store.dispatch(WalletAction.TwinsAction.SetTwinCard( - secondCardId, cardNumber, isCreatingTwinCardsAllowed = true - )) + data.card.getTwinCardNumber()?.let { + store.dispatch(WalletAction.TwinsAction.SetTwinCard(null, it, true)) } } diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt index dc3fc1a618..124ae27387 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Card.kt @@ -3,7 +3,11 @@ package com.tangem.tap.domain.extensions import com.tangem.common.card.Card import com.tangem.common.card.CardWallet import com.tangem.common.extensions.toHexString +import com.tangem.common.services.Result +import com.tangem.operations.attestation.CardVerifyAndGetInfo import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.domain.twins.TwinCardNumber +import com.tangem.tap.domain.twins.getTwinCardNumber import com.tangem.tap.features.wallet.redux.Artwork fun Card.getSingleWallet(): CardWallet? { @@ -24,6 +28,35 @@ fun Card.signedHashesCount(): Int { return wallets.map { it.totalSignedHashes ?: 0 }.sum() } +suspend fun Card.getOrLoadCardArtworkUrl(cardInfo: Result? = null): String { + fun ifAnyError(): String { + return when { + cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL + cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL + else -> { + when (getTwinCardNumber()) { + TwinCardNumber.First -> Artwork.TWIN_CARD_1 + TwinCardNumber.Second -> Artwork.TWIN_CARD_2 + else -> Artwork.DEFAULT_IMG_URL + } + } + } + } + + val cardInfoResult = cardInfo ?: OnlineCardVerifier().getCardInfo(cardId, cardPublicKey) + return when (cardInfoResult) { + is Result.Success -> { + val artworkId = cardInfoResult.data.artwork?.id + if (artworkId == null || artworkId.isEmpty()) { + ifAnyError() + } else { + OnlineCardVerifier.getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId) + } + } + is Result.Failure -> ifAnyError() + } +} + fun Card.getArtworkUrl(artworkId: String?): String? { return when { artworkId != null -> { diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index d879826f12..d2d8aa143c 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -21,7 +21,8 @@ import com.tangem.tap.tangemSdkManager class TwinCardsManager(private val scanResponse: ScanResponse, context: Context) { private val currentCardId: String = scanResponse.card.cardId - private val secondCardId: String? = TwinsHelper.getTwinsCardId(currentCardId) +// private val secondCardId: String? = TwinsHelper.getTwinsCardId(currentCardId) + private val secondCardId: String? = null private var currentCardPublicKey: String? = null private var secondCardPublicKey: String? = null diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt index 8c66c05bfa..65c4d7cb53 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinsHelper.kt @@ -1,7 +1,6 @@ package com.tangem.tap.domain.twins import com.tangem.common.card.Card -import com.tangem.tap.common.extensions.isEven class TwinsHelper { companion object { @@ -21,22 +20,6 @@ class TwinsHelper { } } - fun getTwinsCardId(cardId: String): String? { - val cardIdWithNewSeries = when (getTwinCardNumber(cardId) ?: return null) { - TwinCardNumber.First -> { - val index = if (cardId.startsWith(firstCardSeries[0])) 0 else 1 - cardId.replace(firstCardSeries[index], secondCardSeries[index]) - } - TwinCardNumber.Second -> { - val index = if (cardId.startsWith(secondCardSeries[0])) 0 else 1 - cardId.replace(secondCardSeries[index], firstCardSeries[index]) - } - } - val cardIdWithoutChecksum = cardIdWithNewSeries.dropLast(1) - val checkSum = cardIdWithoutChecksum.calculateLuhn() - return cardIdWithoutChecksum + checkSum - } - fun getTwinCardIdForUser(cardId: String): String { if (cardId.length < 16) return cardId @@ -50,21 +33,6 @@ class TwinsHelper { } } -private fun String.calculateLuhn(): Int { - val checksum = this.reversed() - .mapIndexed { index, c -> - val digit = if (c in '0'..'9') c - '0' else c - 'A' - if (!index.isEven()) { - digit - } else { - val newDigit = digit * 2 - if (newDigit >= 10) newDigit - 9 else newDigit - } - }.sum() - .rem(10) - return (10 - checksum) % 10 -} - enum class TwinCardNumber(val number: Int) { First(1), Second(2); @@ -82,10 +50,6 @@ fun Card.getTwinCardNumber(): TwinCardNumber? { return TwinsHelper.getTwinCardNumber(this.cardId) } -fun Card.getTwinsCardId(): String? { - return TwinsHelper.getTwinsCardId(this.cardId) -} - fun Card.getTwinCardIdForUser(): String { return TwinsHelper.getTwinCardIdForUser(this.cardId) } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 3e8ccb008b..4eb68d750c 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -1,24 +1,19 @@ package com.tangem.tap.features.home.redux import com.tangem.tap.common.entities.IndeterminateProgressButton -import com.tangem.tap.common.extensions.dispatchErrorNotification import com.tangem.tap.common.extensions.dispatchOpenUrl -import com.tangem.tap.common.extensions.withMainContext import com.tangem.tap.common.post import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.common.redux.navigation.FragmentShareTransition import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE -import com.tangem.tap.domain.TapWalletManager -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.features.onboarding.service.OnboardingHelper +import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.send.redux.states.ButtonState -import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.preferencesStorage import com.tangem.tap.scope import com.tangem.tap.store -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.rekotlin.Middleware @@ -52,55 +47,30 @@ 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: ScanResponse) { - scope.launch { - tapWalletManager.onCardScanned(scanResponse) - delay(DELAY_SDK_DIALOG_CLOSE) - withMainContext { changeButtonState(ButtonState.ENABLED) } - 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 + store.dispatch(GlobalAction.ScanCard({ scanResponse -> + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + val navigateTo = OnboardingHelper.whereToNavigate(scanResponse) + store.dispatch(GlobalAction.Onboarding.Start(scanResponse, AppScreen.Home)) + navigateTo(navigateTo, store.state.homeState.shareTransition) + } else { + scope.launch { store.state.globalState.tapWalletManager.onCardScanned(scanResponse) } + navigateTo(AppScreen.Wallet, null) } - - 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) })) } +} + +private fun changeButtonState(state: ButtonState) { + val btnState = IndeterminateProgressButton(state) + store.dispatch(HomeAction.ChangeScanCardButtonState(btnState)) +} + +private fun navigateTo(screen: AppScreen, transition: FragmentShareTransition?) { + post(DELAY_SDK_DIALOG_CLOSE) { + changeButtonState(ButtonState.ENABLED) + store.dispatch(NavigationAction.NavigateTo(screen, transition)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt new file mode 100644 index 0000000000..38b2517ad1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -0,0 +1,27 @@ +package com.tangem.tap.features.onboarding + +import com.tangem.tap.common.redux.navigation.AppScreen +import com.tangem.tap.domain.ProductType +import com.tangem.tap.domain.extensions.hasWallets +import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.preferencesStorage + +/** +[REDACTED_AUTHOR] + */ +class OnboardingHelper { + companion object { + + fun isOnboardingCase(response: ScanResponse): Boolean = if (response.card.hasWallets()) + preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true + + fun whereToNavigate(scanResponse: ScanResponse): AppScreen { + return when (scanResponse.productType) { + ProductType.Note -> AppScreen.OnboardingNote + ProductType.Wallet -> AppScreen.OnboardingWallet + ProductType.Twin -> AppScreen.OnboardingTwins + ProductType.Other -> AppScreen.OnboardingOther + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt new file mode 100644 index 0000000000..62358e80c0 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -0,0 +1,129 @@ +package com.tangem.tap.features.onboarding + +import com.tangem.blockchain.common.AmountType +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.WalletManager +import com.tangem.common.extensions.isZero +import com.tangem.common.services.Result +import com.tangem.operations.attestation.CardVerifyAndGetInfo +import com.tangem.operations.attestation.OnlineCardVerifier +import com.tangem.tap.common.extensions.isPositive +import com.tangem.tap.common.extensions.safeUpdate +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.getOrLoadCardArtworkUrl +import com.tangem.tap.domain.tasks.product.ScanResponse +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.persistence.UsedCardsPrefStorage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.math.BigDecimal +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** +[REDACTED_AUTHOR] + */ +class OnboardingManager( + var scanResponse: ScanResponse, + val fromScreen: AppScreen, + val usedCardsPrefStorage: UsedCardsPrefStorage, +) { + + var cardInfo: Result? = null + private set + + suspend fun loadArtwork(): Artwork { + cardInfo = OnlineCardVerifier().getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey) + return loadArtwork(scanResponse.card.getOrLoadCardArtworkUrl(cardInfo)) + } + + suspend fun loadArtwork(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 -> Artwork(url, null) + } + continuation.resume(result) + } + } + } + } + + suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance { + return 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 currency = Currency.Blockchain(walletManager.wallet.blockchain) + val balance = if (valueOfAmount.isZero()) { + OnboardingWalletBalance.done(valueOfAmount, wallet.recentTransactions.isNotEmpty(), 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) + } + } + } + } + + fun activationStarted(cardId: String) { + usedCardsPrefStorage.activationStarted(cardId) + } + + fun activationFinished(cardId: String) { + usedCardsPrefStorage.activationFinished(cardId) + } +} + +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, +) { + + fun balanceIsToppedUp(): Boolean = value.isPositive() || hasIncomingTransaction + + 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) + } +} \ 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 index b27d17e06a..dde273aa2f 100644 --- 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 @@ -24,7 +24,7 @@ abstract class BaseOnboardingFragment : BaseStoreFragment(R.layout.fragment_o protected var fromScreen: AppScreen? = null override fun onCreate(savedInstanceState: Bundle?) { - fromScreen = store.state.globalState.onboardingService?.fromScreen + fromScreen = store.state.globalState.onboardingManager?.fromScreen super.onCreate(savedInstanceState) } 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 7712bbc3da..97d31afd41 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 @@ -1,13 +1,18 @@ package com.tangem.tap.features.onboarding.products.note +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import android.os.Bundle import android.view.View import android.view.animation.OvershootInterpolator +import android.widget.ImageView import androidx.annotation.LayoutRes import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.isVisible import androidx.transition.TransitionManager import com.tangem.blockchain.common.Blockchain +import com.tangem.tangem_sdk_new.extensions.fadeIn +import com.tangem.tangem_sdk_new.extensions.fadeOut import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.postUi @@ -28,6 +33,7 @@ import kotlinx.android.synthetic.main.view_onboarding_tv_balance.* class OnboardingNoteFragment : BaseOnboardingFragment() { private lateinit var btnRefreshBalanceWidget: RefreshBalanceWidget + private var currentStep: OnboardingNoteStep = OnboardingNoteStep.None override fun getOnboardingTopContainerId(): Int = R.layout.layout_onboarding_note @@ -36,11 +42,12 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { btnRefreshBalanceWidget = RefreshBalanceWidget(onboarding_main_container) val resources = AndroidResources( - OnboardingStringResources( - R.string.copy_toast_msg - ) + OnboardingStringResources( + R.string.copy_toast_msg + ) ) + store.dispatch(OnboardingNoteAction.LoadCardArtwork) store.dispatch(OnboardingNoteAction.SetResources(resources)) store.dispatch(OnboardingNoteAction.DetermineStepOfScreen) } @@ -57,7 +64,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { override fun newState(state: OnboardingNoteState) { if (activity == null) return - state.artworkBitmap?.let { imv_front_card.setImageBitmap(it) } + state.cardArtwork?.let { imv_front_card.swapToBitmapDrawable(it.artwork) } pb_state.max = state.steps.size - 1 pb_state.progress = state.progress @@ -69,13 +76,14 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } setBalance(state) showConfetti(state.showConfetti) + currentStep = state.currentStep } private fun setBalance(state: OnboardingNoteState) { - if (state.balanceCurrency.blockchain == Blockchain.Unknown) return + if (state.walletBalance.currency.blockchain == Blockchain.Unknown) return - val balanceValue = state.balanceValue.stripZeroPlainString() - val currency = state.balanceCurrency.currencySymbol + val balanceValue = state.walletBalance.value.stripZeroPlainString() + val currency = state.walletBalance.currency.currencySymbol tv_balance_value.text = "$balanceValue $currency" } @@ -104,7 +112,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { 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) + updateConstraints(state.currentStep, R.layout.lp_onboarding_create_wallet) } private fun setupTopUpWalletState(state: OnboardingNoteState) { @@ -121,7 +129,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { tv_header.setText(R.string.onboarding_top_up_header) tv_body.setText(R.string.onboarding_top_up_body) - btnRefreshBalanceWidget.changeState(state.balanceState) + btnRefreshBalanceWidget.changeState(state.walletBalance.state) if (btnRefreshBalanceWidget.isShowing != true) { postUi(300) { btnRefreshBalanceWidget.mainView.setOnClickListener { @@ -130,7 +138,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { } } imv_card_background.setBackgroundDrawable(requireContext().getDrawableCompat(R.drawable.shape_rectangle_rounded_8)) - updateConstraints(R.layout.lp_onboarding_topup_wallet) + updateConstraints(state.currentStep, R.layout.lp_onboarding_topup_wallet) } private fun setupDoneState(state: OnboardingNoteState) { @@ -149,10 +157,12 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { 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_activation) + updateConstraints(state.currentStep, R.layout.lp_onboarding_done_activation) } - private fun updateConstraints(@LayoutRes layoutId: Int) { + private fun updateConstraints(currentStep: OnboardingNoteStep, @LayoutRes layoutId: Int) { + if (this.currentStep == currentStep) return + val constraintSet = ConstraintSet() constraintSet.clone(requireContext(), layoutId) constraintSet.applyTo(onboarding_main_container) @@ -160,4 +170,13 @@ class OnboardingNoteFragment : BaseOnboardingFragment() { transition.interpolator = OvershootInterpolator() TransitionManager.beginDelayedTransition(onboarding_main_container, transition) } +} + +fun ImageView.swapToBitmapDrawable(bitmap: Bitmap?, duration: Long = 200) { + if (drawable is BitmapDrawable && (drawable as BitmapDrawable).bitmap == bitmap) return + + fadeOut(duration) { + setImageBitmap(bitmap) + fadeIn(duration) + } } \ 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 9c1644dda1..798f8f4136 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,6 +1,8 @@ package com.tangem.tap.features.onboarding.products.note.redux -import com.tangem.tap.features.onboarding.service.OnboardingWalletBalance +import com.tangem.blockchain.common.WalletManager +import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.Action /** @@ -9,11 +11,14 @@ import org.rekotlin.Action sealed class OnboardingNoteAction : Action { // from user, ui data class SetResources(val resources: AndroidResources) : OnboardingNoteAction() + object LoadCardArtwork : OnboardingNoteAction() object CreateWallet : OnboardingNoteAction() object TopUp : OnboardingNoteAction() object ShowAddressInfoDialog : OnboardingNoteAction() // from redux + class SetArtworkUrl(val artwork: Artwork) : OnboardingNoteAction() + data class SetWalletManager(val walletManager: WalletManager) : OnboardingNoteAction() object DetermineStepOfScreen : OnboardingNoteAction() object Done : OnboardingNoteAction() 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 83ea98d0d0..e63b32b9b4 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,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.common.CompletionResult +import com.tangem.common.extensions.guard import com.tangem.tap.common.extensions.* import com.tangem.tap.common.postUi import com.tangem.tap.common.redux.AppDialog @@ -8,13 +9,16 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction -import com.tangem.tap.features.onboarding.service.balanceIsToppedUp +import com.tangem.tap.domain.TapError +import com.tangem.tap.domain.extensions.hasWallets +import com.tangem.tap.domain.extensions.makePrimaryWalletManager import com.tangem.tap.features.wallet.redux.ProgressState import com.tangem.tap.scope import com.tangem.tap.store import com.tangem.tap.tangemSdkManager import kotlinx.coroutines.launch import org.rekotlin.Action +import org.rekotlin.DispatchFunction import org.rekotlin.Middleware class OnboardingNoteMiddleware { @@ -26,44 +30,57 @@ class OnboardingNoteMiddleware { private val onboardingNoteMiddleware: Middleware = { dispatch, state -> { next -> { action -> - handleNoteAction(action) + handleNoteAction(action, dispatch) next(action) } } } -private fun handleNoteAction(action: Action) { +private fun handleNoteAction(action: Action, dispatch: DispatchFunction) { if (action !is OnboardingNoteAction) return - val service = store.state.onboardingNoteState.onboardingService ?: return + val globalState = store.state.globalState + val onboardingManager = globalState.onboardingManager ?: return + + val scanResponse = onboardingManager.scanResponse + val card = onboardingManager.scanResponse.card val noteState = store.state.onboardingNoteState when (action) { + is OnboardingNoteAction.LoadCardArtwork -> { + scope.launch { + val artwork = onboardingManager.loadArtwork() + withMainContext { store.dispatch(OnboardingNoteAction.SetArtworkUrl(artwork)) } + } + } is OnboardingNoteAction.DetermineStepOfScreen -> { val step = when { - service.walletManager == null -> OnboardingNoteStep.CreateWallet - service.balanceIsToppedUp() -> OnboardingNoteStep.Done + !card.hasWallets() -> OnboardingNoteStep.CreateWallet + noteState.walletBalance.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) } + when (action.step) { + OnboardingNoteStep.TopUpWallet -> { + store.dispatch(OnboardingNoteAction.Balance.Update) + } + OnboardingNoteStep.Done -> { + onboardingManager.activationFinished(card.cardId) + postUi(500) { store.dispatch(OnboardingNoteAction.Confetti.Show) } + } } } is OnboardingNoteAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createProductWallet(service.scanResponse) + val result = tangemSdkManager.createProductWallet(scanResponse) withMainContext { when (result) { is CompletionResult.Success -> { - val updatedResponse = service.scanResponse.copy(card = result.data) - service.scanResponse = updatedResponse - service.activationStarted() - + val updatedResponse = scanResponse.copy(card = result.data) + onboardingManager.scanResponse = updatedResponse + onboardingManager.activationStarted(updatedResponse.card.cardId) store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.TopUpWallet)) - store.dispatch(OnboardingNoteAction.Balance.Update) } is CompletionResult.Failure -> { // do nothing @@ -73,44 +90,57 @@ private fun handleNoteAction(action: Action) { } } is OnboardingNoteAction.Balance.Update -> { - val loadingBalance = service.getBalance().copy( - state = ProgressState.Loading, - error = null, - criticalError = null + val walletManager = if (noteState.walletManager != null) { + noteState.walletManager + } else { + val wmFactory = globalState.tapWalletManager.walletManagerFactory + val walletManager = wmFactory.makePrimaryWalletManager(scanResponse).guard { + val message = "Loading cancelled. Cause: wallet manager didn't created" + val customError = TapError.CustomError(message) + store.dispatchErrorNotification(customError) + return + } + dispatch(OnboardingNoteAction.SetWalletManager(walletManager)) + walletManager + } + + val balanceIsLoading = noteState.walletBalance.copy( + state = ProgressState.Loading, + error = null, + criticalError = null ) - store.dispatch(OnboardingNoteAction.Balance.Set(loadingBalance)) + store.dispatch(OnboardingNoteAction.Balance.Set(balanceIsLoading)) scope.launch { - val loadedBalance = service.updateBalance() - if (loadedBalance.criticalError != null) { - store.dispatchErrorNotification(loadedBalance.criticalError) - } + val loadedBalance = onboardingManager.updateBalance(walletManager) + loadedBalance.criticalError?.let { store.dispatchErrorNotification(it) } withMainContext { store.dispatch(OnboardingNoteAction.Balance.Set(loadedBalance)) } } + } is OnboardingNoteAction.Balance.Set -> { - if (service.balanceIsToppedUp()) { + if (action.balance.balanceIsToppedUp()) { store.dispatch(OnboardingNoteAction.SetStepOfScreen(OnboardingNoteStep.Done)) } } is OnboardingNoteAction.ShowAddressInfoDialog -> { - val addressData = noteState.onboardingService?.getAddressData() ?: return + val addressData = noteState.walletManager?.getAddressData() ?: return val addressWasCopied = noteState.resources.strings.addressWasCopied val appDialog = AppDialog.AddressInfoDialog( - addressData, - onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, - onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } + addressData, + onCopyAddress = { store.dispatchToastNotification(addressWasCopied) }, + onExploreAddress = { store.dispatchOpenUrl(addressData.exploreUrl) } ) store.dispatchDialogShow(appDialog) } is OnboardingNoteAction.TopUp -> { - val topUpUrl = noteState.onboardingService?.getToUpUrl() ?: return + val topUpUrl = noteState.walletManager?.getToUpUrl() ?: return store.dispatchOpenUrl(topUpUrl) } OnboardingNoteAction.Done -> { - store.dispatch(GlobalAction.Onboarding.Deactivate) + store.dispatch(GlobalAction.Onboarding.Stop) scope.launch { - store.state.globalState.tapWalletManager.onCardScanned(service.scanResponse) + globalState.tapWalletManager.onCardScanned(scanResponse) store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } } 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 efe2540b66..03415eec84 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 @@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.onboarding.service.OnboardingNoteService import org.rekotlin.Action class OnboardingNoteReducer { @@ -15,34 +14,20 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt var state = appState.onboardingNoteState when (action) { - 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 - ) - } - } - GlobalAction.Onboarding.Deactivate -> { + is GlobalAction.Onboarding.Start -> { state = OnboardingNoteState() } + is OnboardingNoteAction.SetArtworkUrl -> { + state = state.copy(cardArtwork = action.artwork) + } + is OnboardingNoteAction.SetWalletManager -> { + state = state.copy(walletManager = action.walletManager) + } 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, - ) + state = state.copy(walletBalance = action.balance) } is OnboardingNoteAction.SetStepOfScreen -> { if (action.step != state.currentStep && state.steps.contains(action.step)) { 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 b95c58510d..d4f1c2bcb7 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,28 +1,22 @@ package com.tangem.tap.features.onboarding.products.note.redux -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 com.tangem.blockchain.common.WalletManager +import com.tangem.tap.features.onboarding.OnboardingWalletBalance +import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.StateType -import java.math.BigDecimal /** [REDACTED_AUTHOR] */ data class OnboardingNoteState( - val onboardingService: OnboardingNoteService? = null, + val walletManager: WalletManager? = 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, + // UI + val cardArtwork: Artwork? = null, + val walletBalance: OnboardingWalletBalance = OnboardingWalletBalance.loading(), val currentStep: OnboardingNoteStep = OnboardingNoteStep.None, val steps: List = OnboardingNoteStep.values().toList(), + val showConfetti: Boolean = false, ) : StateType { val progress: Int diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt index 99ce6f4280..3b2dcf7952 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/OnboardingOtherCardsFragment.kt @@ -10,6 +10,7 @@ import androidx.transition.TransitionManager import com.tangem.tap.common.extensions.getDrawableCompat import com.tangem.tap.common.transitions.InternalNoteLayoutTransition import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment +import com.tangem.tap.features.onboarding.products.note.swapToBitmapDrawable import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsAction import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsState import com.tangem.tap.features.onboarding.products.otherCards.redux.OnboardingOtherCardsStep @@ -29,6 +30,7 @@ class OnboardingOtherCardsFragment : BaseOnboardingFragment = { dispatch, state -> { next -> { action -> - handleOtherCardsAction(action) + handleOtherCardsAction(action, dispatch) next(action) } } } -private fun handleOtherCardsAction(action: Action) { +private fun handleOtherCardsAction(action: Action, dispatch: DispatchFunction) { if (action !is OnboardingOtherCardsAction) return - val service = store.state.onboardingOtherCardsState.onboardingService ?: return - val noteState = store.state.onboardingOtherCardsState + val globalState = store.state.globalState + val onboardingManager = globalState.onboardingManager ?: return + + val card = onboardingManager.scanResponse.card when (action) { + is OnboardingOtherCardsAction.LoadCardArtwork -> { + scope.launch { + val artwork = onboardingManager.loadArtwork() + withMainContext { store.dispatch(OnboardingOtherCardsAction.SetArtworkUrl(artwork)) } + } + } is OnboardingOtherCardsAction.DetermineStepOfScreen -> { val step = when { - service.walletManager == null -> OnboardingOtherCardsStep.CreateWallet + !card.hasWallets() -> OnboardingOtherCardsStep.CreateWallet else -> OnboardingOtherCardsStep.Done } store.dispatch((OnboardingOtherCardsAction.SetStepOfScreen(step))) } is OnboardingOtherCardsAction.SetStepOfScreen -> { if (action.step == OnboardingOtherCardsStep.Done) { - service.activationFinished() + onboardingManager.activationFinished(card.cardId) postUi(500) { store.dispatch(OnboardingOtherCardsAction.Confetti.Show) } } } is OnboardingOtherCardsAction.CreateWallet -> { scope.launch { - val result = tangemSdkManager.createProductWallet(service.scanResponse) + val result = tangemSdkManager.createProductWallet(onboardingManager.scanResponse) withMainContext { when (result) { is CompletionResult.Success -> { - val updatedResponse = service.scanResponse.copy(card = result.data) - service.scanResponse = updatedResponse - service.activationStarted() + val updatedResponse = onboardingManager.scanResponse.copy(card = result.data) + onboardingManager.scanResponse = updatedResponse + onboardingManager.activationStarted(updatedResponse.card.cardId) delay(DELAY_SDK_DIALOG_CLOSE) store.dispatch(OnboardingOtherCardsAction.SetStepOfScreen(OnboardingOtherCardsStep.Done)) @@ -71,9 +81,9 @@ private fun handleOtherCardsAction(action: Action) { } } OnboardingOtherCardsAction.Done -> { - store.dispatch(GlobalAction.Onboarding.Deactivate) + store.dispatch(GlobalAction.Onboarding.Stop) scope.launch { - store.state.globalState.tapWalletManager.onCardScanned(service.scanResponse) + globalState.tapWalletManager.onCardScanned(onboardingManager.scanResponse) store.dispatch(NavigationAction.NavigateTo(AppScreen.Wallet)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt index 316f97f636..a6313b9199 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsReducer.kt @@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.otherCards.redux import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction -import com.tangem.tap.features.onboarding.service.OnboardingOtherCardsService import org.rekotlin.Action class OnboardingOtherCardsReducer { @@ -15,19 +14,12 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingOtherC var state = appState.onboardingOtherCardsState when (action) { - is GlobalAction.Onboarding.Activate -> { - if (action.onboardingService is OnboardingOtherCardsService) { - val service = action.onboardingService - state = state.copy( - onboardingService = service, - artworkBitmap = service.getArtwork().value?.artwork, - showConfetti = false - ) - } - } - GlobalAction.Onboarding.Deactivate -> { + is GlobalAction.Onboarding.Start -> { state = OnboardingOtherCardsState() } + is OnboardingOtherCardsAction.SetArtworkUrl -> { + state = state.copy(cardArtwork = action.artwork) + } is OnboardingOtherCardsAction.SetStepOfScreen -> { if (action.step != state.currentStep && state.steps.contains(action.step)) { state = state.copy(currentStep = action.step) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt index bcf232bd85..b306101506 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/otherCards/redux/OnboardingOtherCardsState.kt @@ -1,17 +1,15 @@ package com.tangem.tap.features.onboarding.products.otherCards.redux -import android.graphics.Bitmap -import com.tangem.tap.features.onboarding.service.OnboardingOtherCardsService +import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.StateType /** [REDACTED_AUTHOR] */ data class OnboardingOtherCardsState( - val onboardingService: OnboardingOtherCardsService? = null, - // UI + // UI + val cardArtwork: Artwork? = null, val showConfetti: Boolean = false, - val artworkBitmap: Bitmap? = null, val currentStep: OnboardingOtherCardsStep = OnboardingOtherCardsStep.None, val steps: List = OnboardingOtherCardsStep.values().toList(), ) : StateType { 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 deleted file mode 100644 index 8f7eb6f726..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingHelper.kt +++ /dev/null @@ -1,33 +0,0 @@ -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.ProductType -import com.tangem.tap.domain.extensions.hasWallets -import com.tangem.tap.domain.tasks.product.ScanResponse -import com.tangem.tap.preferencesStorage - -/** -[REDACTED_AUTHOR] - */ -class OnboardingHelper { - companion object { - - fun isOnboardingCase(response: ScanResponse): Boolean = if (response.card.hasWallets()) - preferencesStorage.usedCardsPrefStorage.activationIsStarted(response.card.cardId) else true - - fun createService( - fromScreen: AppScreen, - scanResponse: ScanResponse, - walletManagerFactory: WalletManagerFactory, - ): ProductOnboardingService? { - val cardInfoStorage = preferencesStorage.usedCardsPrefStorage - return when(scanResponse.productType) { - ProductType.Note -> OnboardingNoteService(fromScreen, scanResponse, cardInfoStorage, walletManagerFactory) - ProductType.Wallet -> null - ProductType.Twin -> null - ProductType.Other -> OnboardingOtherCardsService(fromScreen, scanResponse, cardInfoStorage, walletManagerFactory) - } - } - } -} \ 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 deleted file mode 100644 index feaa76eb5c..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingNoteService.kt +++ /dev/null @@ -1,21 +0,0 @@ -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.product.ScanResponse -import com.tangem.tap.persistence.UsedCardsPrefStorage - -/** -[REDACTED_AUTHOR] - */ -class OnboardingNoteService( - override val fromScreen: AppScreen, - override var scanResponse: ScanResponse, - 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/OnboardingOtherCardsService.kt b/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt deleted file mode 100644 index 5e81842312..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/OnboardingOtherCardsService.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.tap.features.onboarding.service - -import com.tangem.blockchain.common.WalletManagerFactory -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.tasks.product.ScanResponse -import com.tangem.tap.features.wallet.redux.AddressData -import com.tangem.tap.persistence.UsedCardsPrefStorage - -/** -[REDACTED_AUTHOR] - */ -class OnboardingOtherCardsService( - override val fromScreen: AppScreen, - override var scanResponse: ScanResponse, - cardInfoStorage: UsedCardsPrefStorage, - walletManagerFactory: WalletManagerFactory, -) : ProductOnboardingService(cardInfoStorage, walletManagerFactory) { - - override fun navigateToScreen(): AppScreen { - return AppScreen.OnboardingOther - } - - // Return any value and any state. The balance of the card is not important in this service - override suspend fun updateBalance(): OnboardingWalletBalance { - val customError = TapError.CustomError("Loading cancelled. Cause: wallet manager didn't created") - withMainContext { loadedBalance = OnboardingWalletBalance.error(customError) } - return loadedBalance - } - - override fun getToUpUrl(): String? { - throw UnsupportedOperationException() - } - - override fun getAddressData(): AddressData? { - throw UnsupportedOperationException() - } -} \ 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 deleted file mode 100644 index 6faff4073e..0000000000 --- a/app/src/main/java/com/tangem/tap/features/onboarding/service/ProductOnboardingService.kt +++ /dev/null @@ -1,291 +0,0 @@ -package com.tangem.tap.features.onboarding.service - -import android.util.Log -import com.tangem.blockchain.common.* -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.product.ScanResponse -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: ScanResponse - - // 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") - withMainContext { 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(wallet), 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(wallet: Wallet): Boolean { - return wallet.recentTransactions.isNotEmpty() - } - - 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/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 51d6c3740d..55e7fb4f66 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -151,7 +151,7 @@ sealed class WalletAction : Action { object ShowOnboarding : TwinsAction() object SetOnboardingShown : TwinsAction() data class SetTwinCard( - val secondCardId: String, val number: TwinCardNumber, + val secondCardId: String?, val number: TwinCardNumber, val isCreatingTwinCardsAllowed: Boolean, ) : TwinsAction() } diff --git a/app/src/main/res/layout/layout_onboarding_note.xml b/app/src/main/res/layout/layout_onboarding_note.xml index e2cb262a46..90cfd95782 100644 --- a/app/src/main/res/layout/layout_onboarding_note.xml +++ b/app/src/main/res/layout/layout_onboarding_note.xml @@ -39,7 +39,7 @@ android:layout_marginStart="32dp" android:layout_marginTop="106dp" android:layout_marginEnd="32dp" - android:src="@drawable/card_placeholder" + android:src="@drawable/ic_home_front_card" android:transitionName="imv_front_card" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.495" diff --git a/app/src/main/res/layout/lp_onboarding_create_wallet.xml b/app/src/main/res/layout/lp_onboarding_create_wallet.xml index 1a45daa73c..076cf8c8ec 100644 --- a/app/src/main/res/layout/lp_onboarding_create_wallet.xml +++ b/app/src/main/res/layout/lp_onboarding_create_wallet.xml @@ -39,7 +39,7 @@ android:layout_marginStart="32dp" android:layout_marginTop="106dp" android:layout_marginEnd="32dp" - android:src="@drawable/card_placeholder" + android:src="@drawable/ic_home_front_card" android:transitionName="imv_front_card" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.495" diff --git a/app/src/main/res/layout/lp_onboarding_done_activation.xml b/app/src/main/res/layout/lp_onboarding_done_activation.xml index a3e9722587..e476f28e3d 100644 --- a/app/src/main/res/layout/lp_onboarding_done_activation.xml +++ b/app/src/main/res/layout/lp_onboarding_done_activation.xml @@ -38,7 +38,7 @@ android:layout_width="140dp" android:layout_height="88dp" android:scaleType="fitXY" - android:src="@drawable/card_placeholder" + android:src="@drawable/ic_home_front_card" android:transitionName="imv_front_card" android:translationY="-30dp" android:visibility="visible" diff --git a/app/src/main/res/layout/lp_onboarding_topup_wallet.xml b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml index 2783b41196..d0261db37a 100644 --- a/app/src/main/res/layout/lp_onboarding_topup_wallet.xml +++ b/app/src/main/res/layout/lp_onboarding_topup_wallet.xml @@ -38,7 +38,7 @@ android:layout_width="140dp" android:layout_height="88dp" android:scaleType="fitXY" - android:src="@drawable/card_placeholder" + android:src="@drawable/ic_home_front_card" android:transitionName="imv_front_card" android:translationY="-30dp" android:visibility="visible"