diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index a118c8e072..8409782c2b 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -279,6 +279,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { onboardingV2FeatureToggles = onboardingV2FeatureToggles, onboardingRepository = onboardingRepository, excludedBlockchains = excludedBlockchains, + appPreferencesStore = appPreferencesStore, ), ), ) 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 0827efeed7..b4df44bdff 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -170,6 +170,12 @@ class DialogManager : StoreSubscriber { ), context = context, ) + is AppDialog.WalletAlreadyWasUsedDialog -> WalletAlreadyWasUsedDialog.create( + context = context, + onOk = state.dialog.onOk, + onSupport = state.dialog.onSupportClick, + onCancel = state.dialog.onCancel, + ) is AppDialog.RemoveWalletDialog -> SimpleCancelableAlertDialog.create( title = context.getString(state.dialog.titleRes, state.dialog.currencyTitle), messageRes = state.dialog.messageRes, diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt b/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt index 04a76036c3..4586881efc 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/AppDialog.kt @@ -48,4 +48,10 @@ sealed class AppDialog : StateDialog { val messageRes: Int = R.string.token_details_unable_hide_alert_message val titleRes: Int = R.string.token_details_unable_hide_alert_title } + + data class WalletAlreadyWasUsedDialog( + val onOk: () -> Unit, + val onSupportClick: () -> Unit, + val onCancel: () -> Unit, + ) : AppDialog() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt index b8515ee5b9..a19ec7e918 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/DefaultScanCardProcessor.kt @@ -32,6 +32,7 @@ internal class DefaultScanCardProcessor : ScanCardProcessor { onProgressStateChange: suspend (showProgress: Boolean) -> Unit, onWalletNotCreated: suspend () -> Unit, disclaimerWillShow: () -> Unit, + onCancel: suspend () -> Unit, onFailure: suspend (error: TangemError) -> Unit, onSuccess: suspend (scanResponse: ScanResponse) -> Unit, ) { @@ -52,6 +53,7 @@ internal class DefaultScanCardProcessor : ScanCardProcessor { onProgressStateChange, onWalletNotCreated, disclaimerWillShow, + onCancel, onFailure, onSuccess, ) diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt index 09ca2e1778..b1810f1745 100644 --- a/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt +++ b/app/src/main/java/com/tangem/tap/domain/scanCard/LegacyScanProcessor.kt @@ -9,18 +9,25 @@ import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic +import com.tangem.datasource.api.tangemTech.models.UserTokensResponse +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.domain.common.TapWorkarounds.canSkipBackup import com.tangem.domain.common.extensions.withMainContext import com.tangem.domain.common.util.twinsIsTwinned +import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.tap.common.analytics.paramsInterceptor.CardContextInterceptor import com.tangem.tap.common.extensions.* +import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.disclaimer.createDisclaimer import com.tangem.tap.features.onboarding.OnboardingHelper import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStartedSource +import com.tangem.tap.mainScope import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store @@ -50,6 +57,7 @@ internal object LegacyScanProcessor { onProgressStateChange: suspend (showProgress: Boolean) -> Unit, onWalletNotCreated: suspend () -> Unit, disclaimerWillShow: () -> Unit, + onCancel: suspend () -> Unit, onFailure: suspend (error: TangemError) -> Unit, onSuccess: suspend (scanResponse: ScanResponse) -> Unit, ) = withMainContext { @@ -81,6 +89,7 @@ internal object LegacyScanProcessor { onProgressStateChange = onProgressStateChange, onSuccess = onSuccess, onWalletNotCreated = onWalletNotCreated, + onCancel = onCancel, ) }, ) @@ -131,36 +140,89 @@ internal object LegacyScanProcessor { scanResponse: ScanResponse, crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit, crossinline onWalletNotCreated: suspend () -> Unit, + crossinline onCancel: suspend () -> Unit, crossinline onSuccess: suspend (ScanResponse) -> Unit, ) { store.dispatchOnMain(TwinCardsAction.IfTwinsPrepareState(scanResponse)) - if (OnboardingHelper.isOnboardingCase(scanResponse)) { - Analytics.addContext(scanResponse) - onWalletNotCreated() - // must check skip backup using card canSkipBackup - store.dispatchOnMain( - GlobalAction.Onboarding.Start( - scanResponse = scanResponse, - source = BackupStartedSource.Onboarding, - canSkipBackup = scanResponse.card.canSkipBackup, + checkCardWasUsedInApp( + scanResponse = scanResponse, + onCancel = { + mainScope.launch { + onProgressStateChange.invoke(false) + onCancel() + } + }, + ) { + if (OnboardingHelper.isOnboardingCase(scanResponse)) { + Analytics.addContext(scanResponse) + onWalletNotCreated() + // must check skip backup using card canSkipBackup + store.dispatchOnMain( + GlobalAction.Onboarding.Start( + scanResponse = scanResponse, + source = BackupStartedSource.Onboarding, + canSkipBackup = scanResponse.card.canSkipBackup, + ), + ) + val appScreen = OnboardingHelper.whereToNavigate(scanResponse) + navigateTo(appScreen) { onProgressStateChange(it) } + } else { + Analytics.setContext(scanResponse) + + val wasTwinsOnboardingShown = + store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase).invokeSync() + + if (scanResponse.twinsIsTwinned() && !wasTwinsOnboardingShown) { + onWalletNotCreated() + store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) + navigateTo(AppRoute.OnboardingTwins) { onProgressStateChange(it) } + } else { + delay(DELAY_SDK_DIALOG_CLOSE) + onSuccess(scanResponse) + } + } + } + } + + /** + * Checks if card has password and never login at this app + * Show alert in this case + */ + private suspend fun checkCardWasUsedInApp( + scanResponse: ScanResponse, + onCancel: () -> Unit, + onSuccess: suspend () -> Unit, + ) { + val userWalletId = runCatching { UserWalletIdBuilder.card(scanResponse.card).build() }.getOrNull() + if (userWalletId == null) { + onSuccess() + return + } + val appPrefStoreStore = store.inject(DaggerGraphState::appPreferencesStore) + val tokens = appPrefStoreStore.getObjectSyncOrNull( + key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue), + ) + if (scanResponse.card.isAccessCodeSet && tokens == null) { + store.dispatchDialogShow( + AppDialog.WalletAlreadyWasUsedDialog( + onOk = { mainScope.launch { onSuccess() } }, + onSupportClick = { + val cardInfo = + store.inject(DaggerGraphState::getCardInfoUseCase).invoke(scanResponse).getOrNull() + ?: error("CardInfo must be not null") + + scope.launch { + store.inject(DaggerGraphState::sendFeedbackEmailUseCase) + .invoke(type = FeedbackEmailType.PreActivatedWallet(cardInfo)) + } + onCancel() + }, + onCancel = { onCancel() }, ), ) - val appScreen = OnboardingHelper.whereToNavigate(scanResponse) - navigateTo(appScreen) { onProgressStateChange(it) } } else { - Analytics.setContext(scanResponse) - - val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase).invokeSync() - - if (scanResponse.twinsIsTwinned() && !wasTwinsOnboardingShown) { - onWalletNotCreated() - store.dispatchOnMain(TwinCardsAction.SetStepOfScreen(TwinCardsStep.WelcomeOnly(scanResponse))) - navigateTo(AppRoute.OnboardingTwins) { onProgressStateChange(it) } - } else { - delay(DELAY_SDK_DIALOG_CLOSE) - onSuccess(scanResponse) - } + onSuccess() } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletAlreadyWasUsedDialog.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletAlreadyWasUsedDialog.kt new file mode 100644 index 0000000000..6d5875be78 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/dialogs/WalletAlreadyWasUsedDialog.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.features.onboarding.products.wallet.ui.dialogs + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.store +import com.tangem.wallet.R + +object WalletAlreadyWasUsedDialog { + + fun create(context: Context, onOk: () -> Unit, onCancel: () -> Unit, onSupport: () -> Unit): AlertDialog { + return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply { + setTitle(R.string.security_alert_title) + setMessage(R.string.wallet_been_activated_message) + setPositiveButton(R.string.this_is_my_wallet_title) { dialog, _ -> + onOk() + dialog.dismiss() + } + setNeutralButton(R.string.common_cancel) { dialog, _ -> + onCancel() + dialog.dismiss() + } + setNegativeButton(R.string.alert_button_request_support) { dialog, _ -> + onSupport() + dialog.dismiss() + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt index 97138375c6..fbae455ce2 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt @@ -19,6 +19,7 @@ internal sealed interface WelcomeAction : Action { object ProceedWithCard : WelcomeAction { object Success : WelcomeAction data class Error(val error: TangemError) : WelcomeAction + data class ChangeProgress(val showProgress: Boolean) : WelcomeAction } data class ProceedWithIntent(val intent: Intent) : WelcomeAction diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 8bd3ee34fc..b9f8660cf1 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -197,6 +197,9 @@ internal class WelcomeMiddleware { } } }, + onProgressStateChange = { + store.dispatchWithMain(WelcomeAction.ProceedWithCard.ChangeProgress(it)) + }, onWalletNotCreated = { store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success) }, diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt index 85db06dc63..35f81371fa 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeReducer.kt @@ -27,6 +27,9 @@ internal object WelcomeReducer { error = action.error, isUnlockWithCardInProgress = false, ) + is WelcomeAction.ProceedWithCard.ChangeProgress -> state.copy( + isUnlockWithCardInProgress = action.showProgress, + ) is WelcomeAction.ProceedWithBiometrics.Success -> state.copy(isUnlockWithBiometricsInProgress = false) is WelcomeAction.ProceedWithCard.Success -> state.copy(isUnlockWithCardInProgress = false) is WelcomeAction.CloseError -> state.copy(error = null) diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 2fda147000..db70565310 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -9,6 +9,7 @@ import com.tangem.core.navigation.url.UrlOpener import com.tangem.datasource.connection.NetworkConnectionManager import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.issuers.IssuersConfigStorage +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.repository.AppThemeModeRepository import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository @@ -75,4 +76,5 @@ data class DaggerGraphState( val onboardingV2FeatureToggles: OnboardingV2FeatureToggles? = null, val onboardingRepository: OnboardingRepository? = null, val excludedBlockchains: ExcludedBlockchains? = null, + val appPreferencesStore: AppPreferencesStore? = null, ) : StateType \ No newline at end of file diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 0576b37e83..4ba37fba49 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -681,6 +681,7 @@ Сканировать Отсканируйте карту или кольцо, чтобы изменить ее настройки. Изменения затронут только ту карту или кольцо, которые вы отсканировали, и не повлияют на другие устройства, привязанные к вашему кошельку. Приготовьте свой Tangem! + Уведомление безопасности На вашем балансе недостаточно средств для продажи этой криптовалюты. Пожалуйста, пополните нужный актив, чтобы продолжить. Недостаточно средств Продажа криптовалюты в вашем регионе временно недоступна. Мы активно работаем над тем, чтобы добавить эту возможность. Следите за нашими новостями! @@ -897,6 +898,7 @@ Вы получите Выберите токен не доступен + Это мой кошелек Балансы скрыты Балансы показаны Отменить @@ -966,6 +968,7 @@ Разблокировать все с %s Блокчейн недоступен. Попробуйте позже. Отсканируйте карту или кольцо + Этот кошелек уже был активирован ранее.\nЕсли это сделали не вы, свяжитесь со службой поддержки.\nTangem никогда не продает кошелек вместе с предустановленным кодом доступа. Запрос на подпись сообщения.\n\n%s Dapp %1$s, запрос на\nподпись транзакции с BNB.\n\n%2$s Торговый ордер на %1$s\nЦена: %2$s\nСумма к получению: %3$s\nСумма к оплате: %4$s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 7d5f44aa6f..f17f29f971 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -319,6 +319,7 @@ Please tell us what card or ring do you have Hi support team, Please tell us more about your issue. Every small detail can help. + Previously activated wallet My suggestions Can\'t scan a card/ring Feedback @@ -663,6 +664,7 @@ Scan card or ring Scan the card or ring to change its settings. The changes will impact only the card or ring you\'ve scanned and will not affect other devices tied to your wallet. Get your Tangem ready! + Security Alert You don’t have enough funds in your balance to sell this cryptocurrency. Please deposit the desired asset to proceed. Insufficient Balance Selling cryptocurrency is unavailable in your region at the moment. We’re actively working to bring this option to you soon—stay tuned! @@ -880,6 +882,7 @@ You receive Choose token not available + This is my wallet Balances hidden Balances shown Undo @@ -949,6 +952,7 @@ Unlock all with %s Blockchain is unreachable. Try later Scan card or ring + This wallet has already been activated earlier.\nIf it was done not by you please contact support.\nTangem never sells wallets along with the pre-generated access code. Requesting to sign a message.\n\n%s Dapp %1$s, requesting to\nsign BNB transaction.\n\n%2$s Trade order for %1$s\nPrice: %2$s\nAmount to receive: %3$s\nAmount to pay: %4$s diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt index ff96eda7f5..9c52d3c84a 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardProcessor.kt @@ -18,6 +18,7 @@ interface ScanCardProcessor { onProgressStateChange: suspend (showProgress: Boolean) -> Unit = {}, onWalletNotCreated: suspend () -> Unit = {}, disclaimerWillShow: () -> Unit = {}, + onCancel: suspend () -> Unit = {}, onFailure: suspend (error: TangemError) -> Unit = {}, onSuccess: suspend (scanResponse: ScanResponse) -> Unit = {}, ) diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt index 2f9578ed40..302680f4c6 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt @@ -57,6 +57,7 @@ class SendFeedbackEmailUseCase( return when (type) { is FeedbackEmailType.ScanningProblem, is FeedbackEmailType.CurrencyDescriptionError, + is FeedbackEmailType.PreActivatedWallet, -> this is FeedbackEmailType.DirectUserRequest, is FeedbackEmailType.RateCanBeBetter, diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt index 70d4017993..2ec928f7ab 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt @@ -46,4 +46,6 @@ sealed interface FeedbackEmailType { data class CurrencyDescriptionError(val currencyId: String, val currencyName: String) : FeedbackEmailType { override val cardInfo: CardInfo? = null } + + data class PreActivatedWallet(override val cardInfo: CardInfo) : FeedbackEmailType } \ No newline at end of file diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt index c6d5bd52eb..9983e4bcc4 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt @@ -26,6 +26,7 @@ internal class EmailMessageBodyResolver( is FeedbackEmailType.StakingProblem -> addStakingProblemBody(type) is FeedbackEmailType.SwapProblem -> addSwapProblemBody(type) is FeedbackEmailType.CurrencyDescriptionError -> addTokenInfo(type) + is FeedbackEmailType.PreActivatedWallet -> addUserRequestBody(type.cardInfo) } return build() diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt index d0768aff1f..1311a3016e 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt @@ -25,6 +25,7 @@ internal class EmailMessageTitleResolver(private val resources: Resources) { is FeedbackEmailType.StakingProblem, is FeedbackEmailType.SwapProblem, -> R.string.feedback_preface_tx_failed + is FeedbackEmailType.PreActivatedWallet -> R.string.feedback_preface_support } .let(resources::getString) } diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt index a833d1b6d3..e373d8d496 100644 --- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt +++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt @@ -29,6 +29,7 @@ internal class EmailSubjectResolver(private val resources: Resources) { is FeedbackEmailType.StakingProblem, is FeedbackEmailType.SwapProblem, -> R.string.feedback_subject_tx_failed + is FeedbackEmailType.PreActivatedWallet -> R.string.feedback_subject_pre_activated_wallet is FeedbackEmailType.CurrencyDescriptionError -> R.string.feedback_token_description_error } .let(resources::getString) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 5b6491d0de..f040d02154 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -15,6 +15,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.isNullOrZero import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* +import kotlinx.coroutines.withTimeoutOrNull /** * Use case to determine which TokenActions are available for a [CryptoCurrency] @@ -45,14 +46,20 @@ class GetCryptoCurrencyActionsUseCase( userWalletId = userWallet.walletId, ) val networkId = cryptoCurrencyStatus.currency.network.id - val requirements = walletManagersFacade.getAssetRequirements(userWallet.walletId, cryptoCurrencyStatus.currency) + val requirements = withTimeoutOrNull(REQUEST_EXCHANGE_DATA_TIMEOUT) { + walletManagersFacade.getAssetRequirements(userWallet.walletId, cryptoCurrencyStatus.currency) + } return flow { val networkFlow = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) { operations.getNetworkCoinForSingleWalletWithTokenFlow(networkId) } else if (!userWallet.isMultiCurrency) { - operations.getPrimaryCurrencyStatusFlow() + operations.getPrimaryCurrencyStatusFlow(includeQuotes = false) } else { - operations.getNetworkCoinFlow(networkId, cryptoCurrencyStatus.currency.network.derivationPath) + operations.getNetworkCoinFlow( + networkId = networkId, + derivationPath = cryptoCurrencyStatus.currency.network.derivationPath, + includeQuotes = false, + ) } val flow = networkFlow.mapLatest { maybeCoinStatus -> @@ -165,10 +172,10 @@ class GetCryptoCurrencyActionsUseCase( // swap if (userWallet.isMultiCurrency) { - if ( - rampManager.availableForSwap(userWallet.walletId, cryptoCurrency) && - cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote - ) { + val isExchangeable = withTimeoutOrNull(REQUEST_EXCHANGE_DATA_TIMEOUT) { + rampManager.availableForSwap(userWallet.walletId, cryptoCurrency) + } ?: false + if (isExchangeable && cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote) { activeList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None)) } else { disabledList.add( @@ -304,4 +311,8 @@ class GetCryptoCurrencyActionsUseCase( cryptoCurrency = cryptoCurrency, ) is StakingAvailability.Available } + + private companion object { + const val REQUEST_EXCHANGE_DATA_TIMEOUT = 1000L + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index 639ccd6991..9a4dcf3728 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -143,13 +143,14 @@ internal class CurrenciesStatusesOperations( suspend fun getNetworkCoinFlow( networkId: Network.ID, derivationPath: Network.DerivationPath, + includeQuotes: Boolean = true, ): Flow> { val currency = recover( block = { getNetworkCoin(networkId, derivationPath) }, recover = { return flowOf(it.left()) }, ) - return getCurrencyStatusFlow(currency) + return getCurrencyStatusFlow(currency, includeQuotes) } suspend fun getNetworkCoinForSingleWalletWithTokenFlow( @@ -163,25 +164,34 @@ internal class CurrenciesStatusesOperations( return getCurrencyStatusFlow(currency) } - suspend fun getPrimaryCurrencyStatusFlow(): Flow> { + suspend fun getPrimaryCurrencyStatusFlow(includeQuotes: Boolean = true): Flow> { val currency = recover( block = { getPrimaryCurrency() }, recover = { return flowOf(it.left()) }, ) - return getCurrencyStatusFlow(currency) + return getCurrencyStatusFlow(currency, includeQuotes) } - fun getCurrencyStatusFlow(currency: CryptoCurrency): Flow> { + fun getCurrencyStatusFlow( + currency: CryptoCurrency, + includeQuotes: Boolean = true, + ): Flow,> { val (networks, currenciesIds) = getIds(nonEmptyListOf(currency)) - val quoteFlow = getQuotes(currenciesIds) - .map { maybeQuotes -> - maybeQuotes.flatMap { quotes -> - quotes.singleOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }?.right() - ?: Error.EmptyQuotes.left() + val quoteFlow = if (includeQuotes) { + getQuotes(currenciesIds) + .map { maybeQuotes -> + maybeQuotes.flatMap { quotes -> + quotes.singleOrNull { it.rawCurrencyId == currency.id.rawCurrencyId }?.right() + ?: Error.EmptyQuotes.left() + } } - } + } else { + // don't use emptyFlow() + flow { emit(Error.EmptyQuotes.left()) } + } val statusFlow = getNetworksStatuses(networks) .map { maybeStatuses -> diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index 3e4f3a77ae..41d94b2cf2 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -85,14 +85,8 @@ internal class CurrencyStatusOperations( } else { null } + // order is important for correct total balance calculation return when { - quote is Quote.Empty || ignoreQuote -> CryptoCurrencyStatus.NoQuote( - amount = amount, - hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, - pendingTransactions = currentTransactions, - networkAddress = status.address, - yieldBalance = currentYieldBalance, - ) currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom( amount = amount, fiatAmount = calculateFiatAmountOrNull(amount, quote?.fiatRate), @@ -103,6 +97,13 @@ internal class CurrencyStatusOperations( networkAddress = status.address, yieldBalance = currentYieldBalance, ) + quote is Quote.Empty || ignoreQuote -> CryptoCurrencyStatus.NoQuote( + amount = amount, + hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, + pendingTransactions = currentTransactions, + networkAddress = status.address, + yieldBalance = currentYieldBalance, + ) quote is Quote.Value -> CryptoCurrencyStatus.Loaded( amount = amount, fiatAmount = calculateFiatAmount(amount, quote.fiatRate), diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a411e3c160..edd1284284 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -42,13 +42,7 @@ platform :android do desc "Build external and release APKs" lane :build do |options| - gradle( - task: "clean assemble", - build_type: "External", - properties: { - 'versionCode' => options[:versionCode], - 'versionName' => options[:versionName], - }) + gradle(task: 'clean') gradle( task: "bundle", build_type: "Release", diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt index a36e126509..ef9905ff46 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt @@ -64,6 +64,6 @@ internal class UserWalletListModel @Inject constructor( } private fun addUserWallet() = withProgress(isWalletSavingInProgress) { - userWalletSaver.scanAndSaveUserWallet() + userWalletSaver.scanAndSaveUserWallet(modelScope) } } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt index 80a7f98ce1..a427f0fc29 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt @@ -1,6 +1,7 @@ package com.tangem.features.details.utils import androidx.compose.ui.res.stringResource +import arrow.core.Either import arrow.core.raise.Raise import arrow.core.raise.ensureNotNull import arrow.core.raise.fold @@ -28,7 +29,11 @@ import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase import com.tangem.features.details.impl.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine import javax.inject.Inject +import kotlin.coroutines.resume @ComponentScoped @Suppress("LongParameterList") @@ -42,21 +47,31 @@ internal class UserWalletSaver @Inject constructor( private val router: Router, ) { - suspend fun scanAndSaveUserWallet() = recover( - block = { - val response = scanCard() ?: return@recover - val userWallet = createUserWallet(response) + suspend fun scanAndSaveUserWallet(scope: CoroutineScope) { + val response = scanCard(scope) + response.fold( + ifLeft = { + val message = it.message - saveWallet(userWallet) - }, - recover = { error -> - val message = error.message + if (!message.isNullOrEmpty()) { + messageSender.send(SnackbarMessage(message)) + } + }, + ifRight = { scanResponse -> + recover(block = { + scanResponse ?: return + val userWallet = createUserWallet(scanResponse) + saveWallet(userWallet) + }, recover = { + val message = it.message - if (!message.isNullOrEmpty()) { - messageSender.send(SnackbarMessage(message)) - } - }, - ) + if (!message.isNullOrEmpty()) { + messageSender.send(SnackbarMessage(message)) + } + },) + }, + ) + } private suspend fun Raise.saveWallet(userWallet: UserWallet) { fold( @@ -105,36 +120,37 @@ internal class UserWalletSaver @Inject constructor( return ensureNotNull(userWallet) { Error.Unknown } } - private suspend fun Raise.scanCard(): ScanResponse? { - var response: ScanResponse? = null + private suspend fun scanCard(scope: CoroutineScope) = suspendCancellableCoroutine { continuation -> + scope.launch { + scanCardProcessor.scan( + analyticsSource = AnalyticsParam.ScreensSources.Settings, + onWalletNotCreated = { + /* no-op */ + }, + disclaimerWillShow = { + continuation.resume(Either.Right(null)) + router.pop() + }, + onSuccess = { + continuation.resume(Either.Right(it)) + }, + onCancel = { + continuation.resume(Either.Right(null)) + }, + onFailure = { tangemError -> + val error = if (!tangemError.silent) { + val message = tangemError.messageResId + ?.let(::resourceReference) + ?: stringReference(tangemError.customMessage) - scanCardProcessor.scan( - analyticsSource = AnalyticsParam.ScreensSources.Settings, - onWalletNotCreated = { - /* no-op */ - }, - disclaimerWillShow = { - router.pop() - }, - onSuccess = { - response = it - }, - onFailure = { tangemError -> - val error = if (!tangemError.silent) { - val message = tangemError.messageResId - ?.let(::resourceReference) - ?: stringReference(tangemError.customMessage) - - Error.Message(message) - } else { - Error.Silent - } - - raise(error) - }, - ) - - return response + Error.Message(message) + } else { + Error.Silent + } + continuation.resume(Either.Left(error)) + }, + ) + } } sealed class Error { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index e0d67ba733..f320c748cb 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -112,8 +112,9 @@ internal class TokenDetailsViewModel @Inject constructor( private val analyticsEventsHandler: AnalyticsEventHandler, private val vibratorHapticManager: VibratorHapticManager, private val clipboardManager: ClipboardManager, - expressStatusFactory: ExpressStatusFactory.Factory, + private val getCryptoCurrencySyncUseCase: GetCryptoCurrencyStatusSyncUseCase, private val onrampFeatureToggles: OnrampFeatureToggles, + expressStatusFactory: ExpressStatusFactory.Factory, getUserWalletUseCase: GetUserWalletUseCase, getStakingIntegrationIdUseCase: GetStakingIntegrationIdUseCase, deepLinksRegistry: DeepLinksRegistry, @@ -212,6 +213,7 @@ internal class TokenDetailsViewModel @Inject constructor( event = TokenScreenAnalyticsEvent.DetailsScreenOpened(token = cryptoCurrency.symbol), ) updateTopBarMenu() + initButtons() updateContent() handleBalanceHiding(owner) } @@ -222,6 +224,21 @@ internal class TokenDetailsViewModel @Inject constructor( super.onCleared() } + private fun initButtons() { + // we need also init buttons before start all loading to avoid buttons blocking + viewModelScope.launch { + val currentCryptoCurrencyStatus = getCryptoCurrencySyncUseCase.invoke( + userWalletId = userWalletId, + cryptoCurrencyId = cryptoCurrency.id, + isSingleWalletWithTokens = false, + ).getOrNull() + currentCryptoCurrencyStatus?.let { + cryptoCurrencyStatus = it + updateButtons(it) + } + } + } + private fun updateContent() { subscribeOnCurrencyStatusUpdates() subscribeOnExpressTransactionsUpdates() diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 6ebbdd5f87..b05595b8f7 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -90,7 +90,7 @@ markdownComposeView = "0.5.4" # region Tangem tangemBlockchainSdk = "release-app_5.19-881" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.19-412" +tangemCardSdk = "release-app_5.19-414" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem17" #tangemVico = "0.0.1" # Keep it! - used for local builds ^