Updated on 2026-08-14
This commit is contained in:
commit
acf110dbcc
30 changed files with 171 additions and 63 deletions
|
|
@ -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<KClass<out Activity>, AppCompatActivity>()
|
||||
private val activities = HashMap<KClass<out Activity>, 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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
private val onActivityResultCallbacks = mutableListOf<OnActivityResultCallback>()
|
||||
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ import timber.log.Timber
|
|||
|
||||
lateinit var store: Store<AppState>
|
||||
|
||||
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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,4 +2,5 @@
|
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -19,7 +19,10 @@
|
|||
<string name="account_archived_recover">回復する</string>
|
||||
<string name="account_archived_recover_dialog_description">「 %1$s 」を回復しようとしています。</string>
|
||||
<string name="account_archived_recover_dialog_title">アカウントを回復する</string>
|
||||
<string name="account_archived_recover_error_message">すでにアクティブアカウント数の上限(20件)を超えています。復元するには1件アーカイブしてください。</string>
|
||||
<string name="account_archived_recover_error_title">アカウントを復元できません</string>
|
||||
<string name="account_archived_title">アーカイブ済み</string>
|
||||
<string name="account_could_not_create">アカウントを作成できませんでした。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="account_create_success_message">アカウントを作成しました</string>
|
||||
<string name="account_details_archive">アカウントをアーカイブする</string>
|
||||
<string name="account_details_archive_action">アーカイブ</string>
|
||||
|
|
@ -258,6 +261,7 @@
|
|||
<string name="common_left">残り%1$s</string>
|
||||
<string name="common_legacy_bitcoin_address">レガシービットコイン</string>
|
||||
<string name="common_locked">ロックされています</string>
|
||||
<string name="common_locked_wallets">ロックされたウォレット</string>
|
||||
<string name="common_main_network">メインネットワーク</string>
|
||||
<string name="common_month">月</string>
|
||||
<string name="common_network_fee_title">ネットワーク手数料</string>
|
||||
|
|
@ -307,6 +311,7 @@
|
|||
<string name="common_sign">署名</string>
|
||||
<string name="common_sign_and_send">署名して送信</string>
|
||||
<string name="common_skip">スキップ</string>
|
||||
<string name="common_something_went_wrong">問題が発生しました</string>
|
||||
<string name="common_stake">ステーキング</string>
|
||||
<string name="common_staking">ステーキング</string>
|
||||
<string name="common_start">始める</string>
|
||||
|
|
@ -1143,7 +1148,7 @@
|
|||
<string name="staking_notification_restake_rewards_text">獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。</string>
|
||||
<string name="staking_notification_restake_text">再ステーキングを使うと、ステーキングを解除することなく、あるバリデータから別のバリデータに資金を移動できます。</string>
|
||||
<string name="staking_notification_stake_entire_balance_text">残高のすべてをステーキングしようとしています。ステーキング解除や報酬請求にかかるネットワーク手数料をカバーするために、少額を残しておくことをお勧めします。</string>
|
||||
<string name="staking_notification_ton_activate_account">TONでステーキングを開始するには、まず任意の金額の送金を行ってください。これにより、ウォレットがアクティブになります。</string>
|
||||
<string name="staking_notification_ton_activate_account">TONのステーキングを開始するには、まず自分のアドレスに少額の取引を送信します。これによりウォレットが有効になります。</string>
|
||||
<string name="staking_notification_ton_extra_reserve_info">取引を完了するには、ネットワーク手数料に加えて最大0.2TONが必要になる場合があります。未使用分は返金されます。</string>
|
||||
<string name="staking_notification_ton_extra_reserve_is_required">この操作を続行するには、ネットワーク手数料に加えて0.2 TONが必要です。残高を補充してください。</string>
|
||||
<string name="staking_notification_ton_extra_reserve_title">TONの準備金が必要です</string>
|
||||
|
|
@ -1214,6 +1219,7 @@
|
|||
<string name="story_web3_title">Web3.0対応</string>
|
||||
<string name="sui_not_enough_coin_for_fee_description">続行するには少なくとも%1$sの受信取引が必要です</string>
|
||||
<string name="sui_not_enough_coin_for_fee_title">残高不足</string>
|
||||
<string name="swap_approve_description">承認すると、このスマートコントラクトが今後の取引でトークンを利用できるようになります。</string>
|
||||
<string name="swap_fixed_rate">固定レート</string>
|
||||
<string name="swap_give_permission_fee_footer">ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。</string>
|
||||
<string name="swap_promo_text">より多くのトークンをより良いレートで、ウォレット内にて直接交換します。</string>
|
||||
|
|
@ -1653,7 +1659,7 @@
|
|||
<string name="yield_module_approve_sheet_title">承認を確定する</string>
|
||||
<string name="yield_module_balance_info_sheet_subtitle">資産残高に関するテキスト [プレースホルダー]</string>
|
||||
<string name="yield_module_balance_info_sheet_title">あなたの%sはAaveに預けられています</string>
|
||||
<string name="yield_module_earn_badge">%s%を獲得</string>
|
||||
<string name="yield_module_earn_badge">%1$s%% を獲得</string>
|
||||
<string name="yield_module_earn_sheet_available_title">利用可能</string>
|
||||
<string name="yield_module_earn_sheet_current_apy_title">現在のAPY</string>
|
||||
<string name="yield_module_earn_sheet_my_funds_title">私の資金</string>
|
||||
|
|
@ -1670,6 +1676,8 @@
|
|||
<string name="yield_module_fee_policy_sheet_title">手数料ポリシー</string>
|
||||
<string name="yield_module_main_view_approve_notification_description">ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー]</string>
|
||||
<string name="yield_module_main_view_approve_notification_title">トークン承認が必要</string>
|
||||
<string name="yield_module_network_fee_unreachable_notification_description">ネットワーク接続を確認してください</string>
|
||||
<string name="yield_module_network_fee_unreachable_notification_title">ネットワーク手数料についての情報にアクセスできません</string>
|
||||
<string name="yield_module_promo_screen_auto_balance_subtitle">アカウントへの入金はすべて自動的にAaveに貸し出されます。</string>
|
||||
<string name="yield_module_promo_screen_auto_balance_title">残高は自動的に計算されます</string>
|
||||
<string name="yield_module_promo_screen_cash_out_subtitle">いつでも、即座に資金を送信、交換、売却できます。</string>
|
||||
|
|
|
|||
|
|
@ -1298,6 +1298,8 @@
|
|||
<string name="wallet_connect_error_wrong_card_selected">Неверная карта или кольцо выбрана в приложении Tangem</string>
|
||||
<string name="wallet_connect_failed_to_build_tx">Не удалось создать транзакцию из данных Dapp. Код: %s</string>
|
||||
<string name="wallet_connect_generic_error_with_code">Произошла непредвиденная ошибка. Код ошибки: %d. Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать — обратитесь в службу поддержки.</string>
|
||||
<string name="wallet_connect_multiple_transactions">Транзакция в несколько шагов</string>
|
||||
<string name="wallet_connect_multiple_transactions_description">Чтобы успешно обработать запрос, ваша транзакция будет разделена на несколько частей. Для завершения потребуется несколько приложений карты.</string>
|
||||
<string name="wallet_connect_no_sessions_message">Нет открытых сессий WalletConnect</string>
|
||||
<string name="wallet_connect_no_sessions_title">Упс. Нет сессий.</string>
|
||||
<string name="wallet_connect_pairing_error">Не удалось создать пару WalletConnect: %1$s</string>
|
||||
|
|
@ -1309,6 +1311,8 @@
|
|||
<string name="wallet_connect_scanner_error_not_valid_card">Эту карту нельзя использовать с WalletConnect.</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">Сеть не поддерживается. Пожалуйста, выберите другую сеть.</string>
|
||||
<string name="wallet_connect_select_network">Выберите сеть</string>
|
||||
<string name="wallet_connect_sending_multiple_explanation">Транзакция в процессе отправки. Пожалуйста, приложите карту несколько раз для её завершения.</string>
|
||||
<string name="wallet_connect_sending_multiple_tx">Транзакция в обработке</string>
|
||||
<string name="wallet_connect_sessions_title">Сессии WalletConnect</string>
|
||||
<string name="wallet_connect_subtitle">Подключение к dApps</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@
|
|||
<string name="account_archived_recover">Recover</string>
|
||||
<string name="account_archived_recover_dialog_description">You’re about to recover “%1$s”.</string>
|
||||
<string name="account_archived_recover_dialog_title">Recover account</string>
|
||||
<string name="account_archived_recover_error_message">You have already exceeded the limit of 20 active accounts. Archive one to recover</string>
|
||||
<string name="account_archived_recover_error_title">Can\'t recover account</string>
|
||||
<string name="account_archived_title">Archived</string>
|
||||
<string name="account_could_not_create">We couldn’t create account. Please try again later.</string>
|
||||
<string name="account_create_success_message">Account created</string>
|
||||
<string name="account_details_archive">Archive account</string>
|
||||
<string name="account_details_archive_action">Archive</string>
|
||||
|
|
@ -314,6 +317,7 @@
|
|||
<string name="common_sign">Sign</string>
|
||||
<string name="common_sign_and_send">Sign and send</string>
|
||||
<string name="common_skip">Skip</string>
|
||||
<string name="common_something_went_wrong">Something went wrong</string>
|
||||
<string name="common_stake">Stake</string>
|
||||
<string name="common_staking">Staking</string>
|
||||
<string name="common_start">Start</string>
|
||||
|
|
@ -927,6 +931,7 @@
|
|||
<string name="receive_bottom_sheet_warning_message_full">Send only %s to this address. Sending any other currency will result in its irreversible loss.</string>
|
||||
<string name="receive_bottom_sheet_warning_title">Send only %1$s on the %2$s network</string>
|
||||
<string name="receive_token_description">Transfer funds from any wallet or exchange</string>
|
||||
<string name="referral_address_for_rewards">Address for rewards</string>
|
||||
<string name="referral_button_participate">Participate</string>
|
||||
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string>
|
||||
|
|
@ -1237,6 +1242,7 @@
|
|||
<string name="story_web3_title">Web 3.0 Compatible</string>
|
||||
<string name="sui_not_enough_coin_for_fee_description">An incoming transaction of at least %1$s is required to proceed</string>
|
||||
<string name="sui_not_enough_coin_for_fee_title">Insufficient funds</string>
|
||||
<string name="swap_approve_description">By approving, you allow the smart contract to use your tokens in future transactions.</string>
|
||||
<string name="swap_fixed_rate">Fixed Rate</string>
|
||||
<string name="swap_give_permission_fee_footer">The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap.</string>
|
||||
<string name="swap_promo_text">Exchange more tokens at better rates directly in your wallet.</string>
|
||||
|
|
@ -1455,8 +1461,8 @@
|
|||
<string name="wallet_connect_error_wrong_card_selected">Wrong card or ring selected in Tangem App</string>
|
||||
<string name="wallet_connect_failed_to_build_tx">Failed to create transaction from Dapp data. Code: %s</string>
|
||||
<string name="wallet_connect_generic_error_with_code">We\'ve encountered unknown error. Error code: %d. If the problem persists — feel free to contact our support</string>
|
||||
<string name="wallet_connect_multiple_transactions">Multiple transactions</string>
|
||||
<string name="wallet_connect_multiple_transactions_description">You’ll need to tap your Tangem device a few times to complete this process.</string>
|
||||
<string name="wallet_connect_multiple_transactions">Multi-Part Transaction</string>
|
||||
<string name="wallet_connect_multiple_transactions_description">To process successfully, your transaction will be split into multiple parts. You\'ll need to tap your card several times to complete it.</string>
|
||||
<string name="wallet_connect_no_sessions_message">No opened WalletConnect sessions</string>
|
||||
<string name="wallet_connect_no_sessions_title">Ooops. No Sessions.</string>
|
||||
<string name="wallet_connect_pairing_error">Failed to pairing WalletConnect session: %1$s</string>
|
||||
|
|
@ -1468,8 +1474,8 @@
|
|||
<string name="wallet_connect_scanner_error_not_valid_card">This card can\'t be used to establish WalletConnect session</string>
|
||||
<string name="wallet_connect_scanner_error_unsupported_network">This network is not supported. Please select another network.</string>
|
||||
<string name="wallet_connect_select_network">Select network</string>
|
||||
<string name="wallet_connect_sending_multiple_explanation">We\'re processing the transaction</string>
|
||||
<string name="wallet_connect_sending_multiple_tx">Sending your funds...</string>
|
||||
<string name="wallet_connect_sending_multiple_explanation">The transaction is being processed. Please tap your card multiple times to complete it.</string>
|
||||
<string name="wallet_connect_sending_multiple_tx">Transaction in progress</string>
|
||||
<string name="wallet_connect_sessions_title">WalletConnect Sessions</string>
|
||||
<string name="wallet_connect_subtitle">Connect to dApps</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
|
|
@ -1725,7 +1731,7 @@
|
|||
<string name="yield_module_approve_sheet_title">Confirm approval</string>
|
||||
<string name="yield_module_balance_info_sheet_subtitle">Text about your money in balance [PLACEHOLDER]</string>
|
||||
<string name="yield_module_balance_info_sheet_title">Your %s is deposited in Aave</string>
|
||||
<string name="yield_module_earn_badge">Earn %s%</string>
|
||||
<string name="yield_module_earn_badge">Earn %1$s%%</string>
|
||||
<string name="yield_module_earn_sheet_available_title">Available</string>
|
||||
<string name="yield_module_earn_sheet_current_apy_title">Current APY</string>
|
||||
<string name="yield_module_earn_sheet_my_funds_title">My Funds</string>
|
||||
|
|
@ -1742,6 +1748,8 @@
|
|||
<string name="yield_module_fee_policy_sheet_title">Fee policy</string>
|
||||
<string name="yield_module_main_view_approve_notification_description">Write description here. In one, two or three lines will be awesome. [PLACEHOLDER]</string>
|
||||
<string name="yield_module_main_view_approve_notification_title">Some token approve needed</string>
|
||||
<string name="yield_module_network_fee_unreachable_notification_description">Check your network connection</string>
|
||||
<string name="yield_module_network_fee_unreachable_notification_title">Network fee info unreachable</string>
|
||||
<string name="yield_module_promo_screen_auto_balance_subtitle">Every top-up of your account will be lended to Aave automatically.</string>
|
||||
<string name="yield_module_promo_screen_auto_balance_title">Your balance works automatically</string>
|
||||
<string name="yield_module_promo_screen_cash_out_subtitle">Send, swap, or sell your funds instantly, anytime you want.</string>
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
|
|||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val country: OnrampCountry,
|
||||
val launchSepa: Boolean,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -15,9 +15,12 @@ import java.math.BigDecimal
|
|||
|
||||
internal class OnrampAmountFieldChangeConverter(
|
||||
private val currentStateProvider: Provider<OnrampMainComponentUM>,
|
||||
) : Converter<String, OnrampMainComponentUM> {
|
||||
) : Converter<OnrampAmountFieldChangeConverter.Input, OnrampMainComponentUM> {
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>): 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<OnrampQuote.Data>()
|
||||
.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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ internal class DefaultOnrampV2MainComponent @AssistedInject constructor(
|
|||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
country = config.country,
|
||||
launchSepa = false,
|
||||
onDismiss = { model.bottomSheetNavigation.dismiss() },
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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() },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue