diff --git a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt index cb7805793c..30db15d90c 100644 --- a/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt +++ b/app/src/main/java/com/tangem/tap/ForegroundActivityObserver.kt @@ -4,30 +4,38 @@ import android.app.Activity import android.app.Application.ActivityLifecycleCallbacks import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import java.util.WeakHashMap +import timber.log.Timber import kotlin.reflect.KClass -class ForegroundActivityObserver { +object ForegroundActivityObserver { - private val activities = WeakHashMap, AppCompatActivity>() + private val activities = HashMap, AppCompatActivity>() val foregroundActivity: AppCompatActivity? get() = activities.entries - .firstOrNull { it.value?.isDestroyed == false } + .firstOrNull { entry -> + Timber.i("foregroundActivity: ${entry.key} | ${entry.value.isDestroyed}") + entry.value.isDestroyed == false + } ?.value internal val callbacks: ActivityLifecycleCallbacks get() = Callbacks() - internal inner class Callbacks : ActivityLifecycleCallbacks { + internal class Callbacks : ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { } override fun onActivityResumed(activity: Activity) { - activities[activity::class] = activity as? AppCompatActivity + Timber.i("onActivityResumed ${activity::class}") + if (activity is AppCompatActivity) { + Timber.i("onActivityResumed store activity") + activities[activity::class] = activity + } } override fun onActivityDestroyed(activity: Activity) { + Timber.i("onActivityDestroyed") activities.remove(activity::class) } diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 650803d430..9d6af9296f 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -191,6 +191,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { private val onActivityResultCallbacks = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { + Timber.i("onCreate") // We need to call it before onCreate to prevent unnecessary activity recreation installAppTheme() @@ -328,15 +329,18 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder { override fun onStart() { super.onStart() + Timber.i("onStart") dialogManager.onStart(this) } override fun onStop() { dialogManager.onStop() super.onStop() + Timber.i("onStop") } override fun onDestroy() { + Timber.i("onDestroy") // workaround: kill process when activity destroy to avoid state when lock() wallets // and navigation to unlock screen was skipped because system kills activity but not process if (BuildConfig.BUILD_TYPE != MOCKED_BUILD_TYPE) { diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 164150ae73..ed454e9111 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -85,7 +85,7 @@ import timber.log.Timber lateinit var store: Store -lateinit var foregroundActivityObserver: ForegroundActivityObserver +val foregroundActivityObserver = ForegroundActivityObserver internal lateinit var derivationsFinder: DerivationsFinder open class TangemApplication : Application(), ImageLoaderFactory, Configuration.Provider { @@ -246,6 +246,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration. override fun onCreate() { enableStrictModeInDebug() + preInit() super.onCreate() init() } @@ -286,22 +287,25 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration. // } } + /** + * Initialize components that need to be initialized before [super.onCreate] is called + */ + private fun preInit() { + tangemAppLoggerInitializer.initialize() + registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) + } + fun init() { apiConfigsManager.initialize() store = createReduxStore() - tangemAppLoggerInitializer.initialize() - Timber.i("APP STARTED") if (BuildConfig.TESTER_MENU_ENABLED) { Timber.i(featureTogglesManager.toString()) Timber.i(excludedBlockchainsManager.toString()) } - foregroundActivityObserver = ForegroundActivityObserver() - registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks) - runBlocking { initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) } diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt index d14d543384..bd7036b570 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt @@ -23,10 +23,12 @@ class AppsFlyerAnalyticsHandler( } class Builder : AnalyticsHandlerBuilder { - override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = when { - !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerApiKey, data.config.appsAppId) - data.isDebug && data.logConfig.appsflyer -> AppsFlyerLogClient(data.jsonConverter) - else -> null - }?.let { AppsFlyerAnalyticsHandler(it) } + override fun build(data: AnalyticsHandlerBuilder.Data): AnalyticsHandler? = null + // disabled for now until analytics strategy is defined + // when { + // !data.isDebug -> AppsFlyerClient(data.application, data.config.appsFlyerApiKey, data.config.appsAppId) + // data.isDebug && data.logConfig.appsflyer -> AppsFlyerLogClient(data.jsonConverter) + // else -> null + // }?.let { AppsFlyerAnalyticsHandler(it) } } } \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6f2acb4f09..8b20aae928 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index aebdb99134..bd9bd6be0d 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -19,7 +19,10 @@ 回復する 「 %1$s 」を回復しようとしています。 アカウントを回復する + すでにアクティブアカウント数の上限(20件)を超えています。復元するには1件アーカイブしてください。 + アカウントを復元できません アーカイブ済み + アカウントを作成できませんでした。しばらくしてからもう一度お試しください。 アカウントを作成しました アカウントをアーカイブする アーカイブ @@ -258,6 +261,7 @@ 残り%1$s レガシービットコイン ロックされています + ロックされたウォレット メインネットワーク ネットワーク手数料 @@ -307,6 +311,7 @@ 署名 署名して送信 スキップ + 問題が発生しました ステーキング ステーキング 始める @@ -1143,7 +1148,7 @@ 獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。 再ステーキングを使うと、ステーキングを解除することなく、あるバリデータから別のバリデータに資金を移動できます。 残高のすべてをステーキングしようとしています。ステーキング解除や報酬請求にかかるネットワーク手数料をカバーするために、少額を残しておくことをお勧めします。 - TONでステーキングを開始するには、まず任意の金額の送金を行ってください。これにより、ウォレットがアクティブになります。 + TONのステーキングを開始するには、まず自分のアドレスに少額の取引を送信します。これによりウォレットが有効になります。 取引を完了するには、ネットワーク手数料に加えて最大0.2TONが必要になる場合があります。未使用分は返金されます。 この操作を続行するには、ネットワーク手数料に加えて0.2 TONが必要です。残高を補充してください。 TONの準備金が必要です @@ -1214,6 +1219,7 @@ Web3.0対応 続行するには少なくとも%1$sの受信取引が必要です 残高不足 + 承認すると、このスマートコントラクトが今後の取引でトークンを利用できるようになります。 固定レート ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。 より多くのトークンをより良いレートで、ウォレット内にて直接交換します。 @@ -1653,7 +1659,7 @@ 承認を確定する 資産残高に関するテキスト [プレースホルダー] あなたの%sはAaveに預けられています - %s%を獲得 + %1$s%% を獲得 利用可能 現在のAPY 私の資金 @@ -1670,6 +1676,8 @@ 手数料ポリシー ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー] トークン承認が必要 + ネットワーク接続を確認してください + ネットワーク手数料についての情報にアクセスできません アカウントへの入金はすべて自動的にAaveに貸し出されます。 残高は自動的に計算されます いつでも、即座に資金を送信、交換、売却できます。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 8c66048afe..979bd1a061 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1298,6 +1298,8 @@ Неверная карта или кольцо выбрана в приложении Tangem Не удалось создать транзакцию из данных Dapp. Код: %s Произошла непредвиденная ошибка. Код ошибки: %d. Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать — обратитесь в службу поддержки. + Транзакция в несколько шагов + Чтобы успешно обработать запрос, ваша транзакция будет разделена на несколько частей. Для завершения потребуется несколько приложений карты. Нет открытых сессий WalletConnect Упс. Нет сессий. Не удалось создать пару WalletConnect: %1$s @@ -1309,6 +1311,8 @@ Эту карту нельзя использовать с WalletConnect. Сеть не поддерживается. Пожалуйста, выберите другую сеть. Выберите сеть + Транзакция в процессе отправки. Пожалуйста, приложите карту несколько раз для её завершения. + Транзакция в обработке Сессии WalletConnect Подключение к dApps WalletConnect diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index e28fc2190d..5727c04eb0 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -19,7 +19,10 @@ Recover You’re about to recover “%1$s”. Recover account + You have already exceeded the limit of 20 active accounts. Archive one to recover + Can\'t recover account Archived + We couldn’t create account. Please try again later. Account created Archive account Archive @@ -314,6 +317,7 @@ Sign Sign and send Skip + Something went wrong Stake Staking Start @@ -927,6 +931,7 @@ Send only %s to this address. Sending any other currency will result in its irreversible loss. Send only %1$s on the %2$s network Transfer funds from any wallet or exchange + Address for rewards Participate Failed to load the information about the referral program. Please try again later. Failed to load the information about the referral program. Error code: %s. Please try again later. @@ -1237,6 +1242,7 @@ Web 3.0 Compatible An incoming transaction of at least %1$s is required to proceed Insufficient funds + By approving, you allow the smart contract to use your tokens in future transactions. Fixed Rate The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. Exchange more tokens at better rates directly in your wallet. @@ -1455,8 +1461,8 @@ Wrong card or ring selected in Tangem App Failed to create transaction from Dapp data. Code: %s We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support - Multiple transactions - You’ll need to tap your Tangem device a few times to complete this process. + Multi-Part Transaction + To process successfully, your transaction will be split into multiple parts. You\'ll need to tap your card several times to complete it. No opened WalletConnect sessions Ooops. No Sessions. Failed to pairing WalletConnect session: %1$s @@ -1468,8 +1474,8 @@ This card can\'t be used to establish WalletConnect session This network is not supported. Please select another network. Select network - We\'re processing the transaction - Sending your funds... + The transaction is being processed. Please tap your card multiple times to complete it. + Transaction in progress WalletConnect Sessions Connect to dApps WalletConnect @@ -1725,7 +1731,7 @@ Confirm approval Text about your money in balance [PLACEHOLDER] Your %s is deposited in Aave - Earn %s% + Earn %1$s%% Available Current APY My Funds @@ -1742,6 +1748,8 @@ Fee policy Write description here. In one, two or three lines will be awesome. [PLACEHOLDER] Some token approve needed + Check your network connection + Network fee info unreachable Every top-up of your account will be lended to Aave automatically. Your balance works automatically Send, swap, or sell your funds instantly, anytime you want. diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt index 2a3d2c624f..89a968a5fd 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt @@ -14,6 +14,7 @@ import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.transaction.usecase.PrepareAndSignUseCase import com.tangem.domain.transaction.usecase.SendLargeSolanaTransactionUseCase +import com.tangem.domain.walletconnect.WcAnalyticEvents.SolanaLargeTransactionStatus import com.tangem.domain.walletconnect.error.parseSendError import com.tangem.domain.walletconnect.model.WcSolanaMethod import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck @@ -59,10 +60,12 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor( sendLargeSolanaTransactionUseCase(context.session.wallet as UserWallet.Cold, context.network, formattedHash) .fold( ifLeft = { + analytics.send(SolanaLargeTransactionStatus(SolanaLargeTransactionStatus.Status.Failed)) Timber.e(it.toString()) emit(state.toResult(parseSendError(it).left())) }, ifRight = { + analytics.send(SolanaLargeTransactionStatus(SolanaLargeTransactionStatus.Status.Success)) val emptyRespond = ByteArray(0).formatAsSolanaSignature() val respondResult = respondService.respond(rawSdkRequest, emptyRespond) emit(state.toResult(respondResult)) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt index d7b1631aeb..435abeeffc 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt @@ -110,6 +110,10 @@ sealed interface StakingIntegrationID { val integrationId = blockchain.integrationId ?: return null + if (integrationId is Coin && currencyId.contractAddress != null) { + return null + } + return when (integrationId) { is Coin -> integrationId is EthereumToken -> { diff --git a/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIntegrationIDTest.kt b/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIntegrationIDTest.kt index 965179516f..74095fb3da 100644 --- a/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIntegrationIDTest.kt +++ b/domain/staking/src/test/kotlin/com/tangem/domain/staking/StakingIntegrationIDTest.kt @@ -139,9 +139,13 @@ class StakingIntegrationIDTest { expected = StakingIntegrationID.Coin.Cardano, ), CreateModel( - currencyId = CryptoCurrency.ID.fromValue(value = "coin⟨ETH⟩polygon-ecosystem-token⚓"), + currencyId = CryptoCurrency.ID.fromValue(value = "token⟨ETH⟩polygon-ecosystem-token⚓1234567890"), expected = StakingIntegrationID.EthereumToken.Polygon, ), + CreateModel( + currencyId = CryptoCurrency.ID.fromValue(value = "token⟨SOLANA⟩solana⚓1234567890"), + expected = null, + ), ) } diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt index b7228f2980..1ccecda5d1 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt @@ -236,6 +236,29 @@ sealed class WcAnalyticEvents( enum class Source { Domain, SmartContract } } + data class SolanaLargeTransaction( + val dappName: String, + ) : WcAnalyticEvents( + event = "Solana Large Transaction", + params = mapOf( + AnalyticsParam.Key.DAPP_NAME to dappName, + ), + ) + + data class SolanaLargeTransactionStatus( + val status: Status, + ) : WcAnalyticEvents( + event = "Solana Large Transaction Status", + params = mapOf( + AnalyticsParam.Key.STATUS to status.value, + ), + ) { + enum class Status(val value: String) { + Success("Success"), + Failed("Failed"), + } + } + enum class DAppVerificationStatus(val status: String) { Verified("Verified"), Risky("Risky"), diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt index df3299765b..6942e44992 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/ConfirmResidencyComponent.kt @@ -12,6 +12,7 @@ internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent { val userWalletId: UserWalletId, val cryptoCurrency: CryptoCurrency, val country: OnrampCountry, + val launchSepa: Boolean, val onDismiss: () -> Unit, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index 3466152b67..724ec2e7a4 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -9,12 +9,14 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase +import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.utils.model.EUR_CURRENCY import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch @@ -26,6 +28,7 @@ internal class ConfirmResidencyModel @Inject constructor( private val analyticsEventHandler: AnalyticsEventHandler, private val router: Router, private val saveDefaultCountryUseCase: OnrampSaveDefaultCountryUseCase, + private val onrampSaveDefaultCurrencyUseCase: OnrampSaveDefaultCurrencyUseCase, paramsContainer: ParamsContainer, ) : Model() { @@ -54,6 +57,10 @@ internal class ConfirmResidencyModel @Inject constructor( analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name)) modelScope.launch { saveDefaultCountryUseCase.invoke(country) + if (params.launchSepa) { + onrampSaveDefaultCurrencyUseCase.invoke(EUR_CURRENCY) + } + params.onDismiss() } }, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt index 0f959bb2c2..d03c60d6d0 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/DefaultOnrampMainComponent.kt @@ -57,6 +57,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor( userWalletId = params.userWalletId, cryptoCurrency = params.cryptoCurrency, country = config.country, + launchSepa = params.launchSepa, onDismiss = { model.bottomSheetNavigation.dismiss() model.handleOnrampAvailable() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt index 5654a17174..0115a77152 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampIntents.kt @@ -3,7 +3,7 @@ package com.tangem.features.onramp.main.entity import com.tangem.domain.onramp.model.OnrampProviderWithQuote interface OnrampIntents { - fun onAmountValueChanged(value: String) + fun onAmountValueChanged(value: String, isValuePasted: Boolean) fun openSettings() fun openCurrenciesList() fun onBuyClick(quote: OnrampProviderWithQuote.Data) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt index bc6113303c..9bcc516c6b 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt @@ -1,8 +1,10 @@ package com.tangem.features.onramp.main.entity import com.tangem.domain.onramp.model.OnrampAmount +import com.tangem.domain.onramp.model.OnrampPaymentMethod data class OnrampLastUpdate( - val lastAmount: OnrampAmount, - val lastCountryString: String, + val fromAmount: OnrampAmount, + val countryCode: String, + val paymentMethod: OnrampPaymentMethod, ) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt index bfd3db959d..09da044da3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt @@ -122,7 +122,7 @@ internal class OnrampStateFactory( amountFieldModel = AmountFieldModel( value = "", fiatValue = "", - onValueChange = onrampIntents::onAmountValueChanged, + onValueChange = { onrampIntents.onAmountValueChanged(value = it, isValuePasted = false) }, keyboardOptions = KeyboardOptions( imeAction = ImeAction.None, keyboardType = KeyboardType.Number, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt index 576bbe719e..02c3f9fa33 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountFieldChangeConverter.kt @@ -15,9 +15,12 @@ import java.math.BigDecimal internal class OnrampAmountFieldChangeConverter( private val currentStateProvider: Provider, -) : Converter { +) : Converter { + + override fun convert(input: Input): OnrampMainComponentUM { + val value = input.value + val isValuePasted = input.isValuePasted - override fun convert(value: String): OnrampMainComponentUM { val state = currentStateProvider() if (state !is OnrampMainComponentUM.Content) return state @@ -34,6 +37,7 @@ internal class OnrampAmountFieldChangeConverter( imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None, keyboardType = KeyboardType.Number, ), + isValuePasted = isValuePasted, ) return state.copy( @@ -66,4 +70,6 @@ internal class OnrampAmountFieldChangeConverter( providerBlockState = OnrampProviderBlockUM.Empty, ) } + + data class Input(val value: String, val isValuePasted: Boolean) } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt index 2ad9356378..4d466907aa 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -33,7 +33,8 @@ internal class OnrampAmountStateFactory( currentStateProvider = currentStateProvider, ) - fun getOnAmountValueChange(value: String) = onrampAmountFieldChangeConverter.convert(value) + fun getOnAmountValueChange(value: String, isValuePasted: Boolean) = + onrampAmountFieldChangeConverter.convert(OnrampAmountFieldChangeConverter.Input(value, isValuePasted)) fun getUpdatedCurrencyState(currency: OnrampCurrency): OnrampMainComponentUM { val currentState = currentStateProvider() diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index 832304f55a..6b3a4d73a6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -32,6 +32,7 @@ import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory.Companion.PREDEFINED_SEPA_AMOUNT import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory import com.tangem.features.onramp.providers.entity.SelectProviderResult +import com.tangem.features.onramp.utils.model.EUR_CURRENCY import com.tangem.features.onramp.utils.sendOnrampErrorEvent import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -68,7 +69,7 @@ internal class OnrampMainComponentModel @Inject constructor( private val params: OnrampMainComponent.Params = paramsContainer.require() - private var isSepaLaunched = false + private var shouldForceChooseSepa = params.launchSepa private var currencyToRestore: OnrampCurrency? = null val userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId } @@ -132,6 +133,10 @@ internal class OnrampMainComponentModel @Inject constructor( fun onProviderSelected(result: SelectProviderResult, isBestRate: Boolean) { _state.update { amountStateFactory.getAmountSecondaryUpdatedState(result, isBestRate) } + + if (result.paymentMethod.id != SEPA_METHOD_ID) { + shouldForceChooseSepa = false + } } private fun checkResidenceCountry() { @@ -171,7 +176,7 @@ internal class OnrampMainComponentModel @Inject constructor( updatePairsAndQuotes() if (wasInitialLoading && params.launchSepa) { - onAmountValueChanged(PREDEFINED_SEPA_AMOUNT) + onAmountValueChanged(value = PREDEFINED_SEPA_AMOUNT, isValuePasted = true) } }, ) @@ -243,8 +248,8 @@ internal class OnrampMainComponentModel @Inject constructor( .launchIn(modelScope) } - override fun onAmountValueChanged(value: String) { - _state.update { amountStateFactory.getOnAmountValueChange(value) } + override fun onAmountValueChanged(value: String, isValuePasted: Boolean) { + _state.update { amountStateFactory.getOnAmountValueChange(value, isValuePasted) } modelScope.launch { amountInputManager.update(value) } } @@ -341,9 +346,7 @@ internal class OnrampMainComponentModel @Inject constructor( private fun selectOrUpdateQuote(quotes: List): OnrampQuote? { val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error } - val sepaQuote = if (params.launchSepa && !isSepaLaunched) { - isSepaLaunched = true - + val bestSepaQuote = if (params.launchSepa && shouldForceChooseSepa) { quotes.filterIsInstance() .filter { it.paymentMethod.id == SEPA_METHOD_ID } .maxByOrNull { it.toAmount.value } @@ -352,7 +355,7 @@ internal class OnrampMainComponentModel @Inject constructor( } // Check if amount, country or currency has changed - val newQuote = sepaQuote ?: if (checkLastInputState(quoteToCheck)) { + val newQuote = bestSepaQuote ?: if (isAmountOrCountryChanged(quoteToCheck)) { quoteToCheck } else { val state = state.value as? OnrampMainComponentUM.Content @@ -371,7 +374,9 @@ internal class OnrampMainComponentModel @Inject constructor( lastSelectedQuote } } - newQuote?.let { updateProvider(newQuote, quotes) } + if (newQuote != null) { + updateProvider(newQuote, quotes) + } return newQuote } @@ -380,8 +385,13 @@ internal class OnrampMainComponentModel @Inject constructor( lastUpdateState.value = OnrampLastUpdate( quote.fromAmount, quote.countryCode, + quote.paymentMethod, ) + if (quote.paymentMethod.id != SEPA_METHOD_ID) { + shouldForceChooseSepa = false + } + _state.update { amountStateFactory.getUpdatedProviderState(selectedQuote = quote, quotes = quotes) } @@ -447,22 +457,14 @@ internal class OnrampMainComponentModel @Inject constructor( } } - private fun checkLastInputState(quote: OnrampQuote?): Boolean { - return lastUpdateState.value?.lastAmount != quote?.fromAmount || - lastUpdateState.value?.lastCountryString != quote?.countryCode + private fun isAmountOrCountryChanged(quote: OnrampQuote?): Boolean { + return lastUpdateState.value?.fromAmount != quote?.fromAmount || + lastUpdateState.value?.countryCode != quote?.countryCode } private companion object { const val UPDATE_DELAY = 10_000L const val SEPA_METHOD_ID = "sepa" - - val EUR_CURRENCY = OnrampCurrency( - code = "EUR", - name = "Euro", - unit = "€", - precision = 2, - image = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/Currencies/EUR.png", - ) } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/DefaultOnrampV2MainComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/DefaultOnrampV2MainComponent.kt index af62d7ecbe..008cf19772 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/DefaultOnrampV2MainComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/DefaultOnrampV2MainComponent.kt @@ -58,6 +58,7 @@ internal class DefaultOnrampV2MainComponent @AssistedInject constructor( userWalletId = params.userWalletId, cryptoCurrency = params.cryptoCurrency, country = config.country, + launchSepa = false, onDismiss = { model.bottomSheetNavigation.dismiss() }, ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt index 93f199fc85..834518bbb3 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/mainv2/model/OnrampV2MainComponentModel.kt @@ -348,8 +348,9 @@ internal class OnrampV2MainComponentModel @Inject constructor( private fun updateProvider(quote: OnrampQuote) { lastUpdateState.value = OnrampLastUpdate( - quote.fromAmount, - quote.countryCode, + fromAmount = quote.fromAmount, + countryCode = quote.countryCode, + paymentMethod = quote.paymentMethod, ) _state.update { @@ -378,8 +379,8 @@ internal class OnrampV2MainComponentModel @Inject constructor( } private fun checkLastInputState(quote: OnrampQuote?): Boolean { - return lastUpdateState.value?.lastAmount != quote?.fromAmount || - lastUpdateState.value?.lastCountryString != quote?.countryCode + return lastUpdateState.value?.fromAmount != quote?.fromAmount || + lastUpdateState.value?.countryCode != quote?.countryCode } private fun sendScreenOpenAnalytics() { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/model/EurCurrency.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/model/EurCurrency.kt new file mode 100644 index 0000000000..6f75fc6908 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/utils/model/EurCurrency.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp.utils.model + +import com.tangem.domain.onramp.model.OnrampCurrency + +internal val EUR_CURRENCY = OnrampCurrency( + code = "EUR", + name = "Euro", + unit = "€", + precision = 2, + image = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/Currencies/EUR.png", +) \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index eaeed60bf4..801a7ca8cb 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -510,7 +510,7 @@ internal class SendConfirmModel @Inject constructor( val txUrl = getExplorerTransactionUrlUseCase( txHash = txData.hash.orEmpty(), networkId = cryptoCurrency.network.id, - ).getOrElse { "" } + ).getOrNull().orEmpty() _uiState.update(SendConfirmSentStateTransformer(txData, txUrl)) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/model/SendConfirmSuccessModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/model/SendConfirmSuccessModel.kt index 80bdafb04f..0d6a56bba6 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/model/SendConfirmSuccessModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/success/model/SendConfirmSuccessModel.kt @@ -76,7 +76,7 @@ internal class SendConfirmSuccessModel @Inject constructor( }, ), prevButton = null, - secondaryPairButtonsUM = NavigationButton( + secondaryPairButtonsUM = (NavigationButton( textReference = resourceReference(R.string.common_explore), iconRes = R.drawable.ic_web_24, onClick = ::onExploreClick, @@ -84,7 +84,7 @@ internal class SendConfirmSuccessModel @Inject constructor( textReference = resourceReference(R.string.common_share), iconRes = R.drawable.ic_share_24, onClick = ::onShareClick, - ), + )).takeIf { params.txUrl.isNotEmpty() }, ), ), ) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt index bd7ff87906..7204187015 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt @@ -498,7 +498,7 @@ internal class NFTSendConfirmModel @Inject constructor( iconRes = R.drawable.ic_share_24, onClick = ::onShareClick, ) - ).takeIf { confirmUM is ConfirmUM.Success }, + ).takeUnless { (confirmUM as? ConfirmUM.Success)?.txUrl.isNullOrBlank() }, ), ), ) diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt index 63ad5d40da..33909f7563 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/model/SendWithSwapSuccessModel.kt @@ -52,7 +52,7 @@ internal class SendWithSwapSuccessModel @Inject constructor( onClick = appRouter::pop, ), prevButton = null, - secondaryPairButtonsUM = NavigationButton( + secondaryPairButtonsUM = (NavigationButton( textReference = resourceReference(R.string.common_explore), iconRes = R.drawable.ic_web_24, onClick = ::onExploreClick, @@ -60,7 +60,7 @@ internal class SendWithSwapSuccessModel @Inject constructor( textReference = resourceReference(R.string.common_share), iconRes = R.drawable.ic_share_24, onClick = ::onShareClick, - ), + )).takeUnless { confirmUM?.txUrl.isNullOrBlank() }, ), ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 7a39b927cd..8775176d12 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -410,7 +410,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( rateType = RateType.FLOAT, ) - return if (isBalanceWithoutFeeEnough) { + return if (isBalanceWithoutFeeEnough && maybeQuotes.isRight()) { provider to loadDexSwapData( provider = provider, networkId = networkId, diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index 99b80940ee..8d36bbe2c6 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -32,6 +32,7 @@ import com.tangem.domain.transaction.error.SendTransactionError.UserCancelledErr import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.walletconnect.WcAnalyticEvents import com.tangem.domain.walletconnect.WcAnalyticEvents.SignatureRequestReceived.EmulationStatus +import com.tangem.domain.walletconnect.WcAnalyticEvents.SolanaLargeTransaction import com.tangem.domain.walletconnect.WcRequestUseCaseFactory import com.tangem.domain.walletconnect.model.WcRequestError import com.tangem.domain.walletconnect.model.WcRequestError.Companion.message @@ -143,6 +144,7 @@ internal class WcSendTransactionModel @Inject constructor( wcApproval = useCase as? WcApproval sign = { if (isMultipleSignRequired(useCase)) { + analytics.send(SolanaLargeTransaction(useCase.rawSdkRequest.dAppMetaData.name)) openMultipleTransaction(useCase) } else { useCase.sign()