Updated on 2026-08-14
This commit is contained in:
commit
9d71729f72
35 changed files with 347 additions and 203 deletions
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.os.bundleOf
|
||||
|
|
@ -20,11 +21,15 @@ internal class DecomposeFragment : ComposeFragment() {
|
|||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val component by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
private lateinit var component: ComposableContentComponent
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val tag = requireArguments().getString(TAG_KEY)
|
||||
val builder = componentsBuilders[tag]
|
||||
|
||||
requireNotNull(builder?.build()) {
|
||||
component = requireNotNull(builder?.build()) {
|
||||
"Component builder is not set, call newInstance() for DecomposeFragment creation first."
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap
|
|||
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
|
|
@ -17,6 +16,7 @@ internal class LockUserWalletsTimer(
|
|||
private val settingsRepository: SettingsRepository,
|
||||
private val duration: Duration = with(Duration) { 10.minutes },
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
) : LifecycleOwner by owner,
|
||||
DefaultLifecycleObserver {
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ internal class LockUserWalletsTimer(
|
|||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
owner.lifecycleScope.launch {
|
||||
coroutineScope.launch {
|
||||
val wasApplicationStopped = settingsRepository.wasApplicationStopped()
|
||||
val shouldOpenWelcomeScreenOnResume = settingsRepository.shouldOpenWelcomeScreenOnResume()
|
||||
|
||||
|
|
@ -59,16 +59,11 @@ internal class LockUserWalletsTimer(
|
|||
override fun onStop(owner: LifecycleOwner) {
|
||||
Timber.i("Owner stopped")
|
||||
|
||||
owner.lifecycleScope.launch {
|
||||
coroutineScope.launch {
|
||||
settingsRepository.setWasApplicationStopped(value = true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
Timber.i("Owner destroyed")
|
||||
stop()
|
||||
}
|
||||
|
||||
fun restart() {
|
||||
if (delayJob == null) return
|
||||
Timber.i(
|
||||
|
|
@ -92,44 +87,30 @@ internal class LockUserWalletsTimer(
|
|||
delayJob = createDelayJob()
|
||||
}
|
||||
|
||||
private fun stop(log: Boolean = true) {
|
||||
if (log) {
|
||||
Timber.i(
|
||||
"""
|
||||
Timer stop
|
||||
|- Was started: ${delayJob?.isActive ?: false}
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
delayJob = null
|
||||
}
|
||||
|
||||
private fun createDelayJob(): Job = lifecycleScope.launch(Dispatchers.Default) {
|
||||
private fun createDelayJob(): Job = coroutineScope.launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
delay(duration)
|
||||
|
||||
if (isActive) {
|
||||
val userWalletsListManager = userWalletsListManager.asLockable() ?: return@launch
|
||||
val userWalletsListManager = userWalletsListManager.asLockable() ?: return@launch
|
||||
|
||||
if (userWalletsListManager.hasUserWallets) {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val wasApplicationStopped = settingsRepository.wasApplicationStopped()
|
||||
if (userWalletsListManager.hasUserWallets) {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val wasApplicationStopped = settingsRepository.wasApplicationStopped()
|
||||
|
||||
Timber.i(
|
||||
"""
|
||||
Timber.i(
|
||||
"""
|
||||
Finished
|
||||
|- App is stopped: $wasApplicationStopped
|
||||
|- Millis passed: ${currentTime - startTime}
|
||||
""".trimIndent(),
|
||||
)
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
userWalletsListManager.lock()
|
||||
if (wasApplicationStopped) {
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
|
||||
} else {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
userWalletsListManager.lock()
|
||||
if (wasApplicationStopped) {
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
|
||||
} else {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
context = rootComponentContext,
|
||||
)
|
||||
|
||||
appRouterConfig.routerScope = lifecycleScope
|
||||
appRouterConfig.routerScope = mainScope
|
||||
appRouterConfig.componentRouter = routingComponent.router
|
||||
appRouterConfig.snackbarHandler = this
|
||||
|
||||
|
|
@ -353,6 +353,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
owner = this,
|
||||
settingsRepository = settingsRepository,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
coroutineScope = mainScope,
|
||||
)
|
||||
|
||||
initIntentHandlers()
|
||||
|
|
@ -433,6 +434,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
override fun onDestroy() {
|
||||
intentProcessor.removeAll()
|
||||
// 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
|
||||
android.os.Process.killProcess(android.os.Process.myPid())
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.domain.card.repository.DerivationsRepository
|
|||
import com.tangem.domain.managetokens.*
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -65,6 +66,7 @@ internal object ManageTokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
derivationsRepository: DerivationsRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
): SaveManagedTokensUseCase {
|
||||
return SaveManagedTokensUseCase(
|
||||
customTokensRepository = customTokensRepository,
|
||||
|
|
@ -72,6 +74,7 @@ internal object ManageTokensDomainModule {
|
|||
currenciesRepository = currenciesRepository,
|
||||
networksRepository = networksRepository,
|
||||
derivationsRepository = derivationsRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ internal class ChildFactory @Inject constructor(
|
|||
params = StakingComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
cryptoCurrencyId = route.cryptoCurrencyId,
|
||||
yield = route.yield,
|
||||
yieldId = route.yieldId,
|
||||
),
|
||||
componentFactory = stakingComponentFactory,
|
||||
)
|
||||
|
|
@ -561,7 +561,7 @@ internal class ChildFactory @Inject constructor(
|
|||
params = StakingComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
cryptoCurrencyId = route.cryptoCurrencyId,
|
||||
yield = route.yield,
|
||||
yieldId = route.yieldId,
|
||||
),
|
||||
componentFactory = stakingComponentFactory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import com.tangem.domain.markets.TokenMarketParams
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
|
@ -186,8 +185,8 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data class Staking(
|
||||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrencyId: CryptoCurrency.ID,
|
||||
val yield: Yield,
|
||||
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/${yield.id}")
|
||||
val yieldId: String,
|
||||
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrencyId.value}/$yieldId")
|
||||
|
||||
@Serializable
|
||||
data object PushNotification : AppRoute(path = "/push_notification")
|
||||
|
|
|
|||
|
|
@ -72,21 +72,35 @@ internal class DefaultStakingBalanceStore(
|
|||
override suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
|
||||
coroutineScope {
|
||||
launch {
|
||||
updateRuntimeStore(userWalletId = userWalletId) {
|
||||
YieldBalanceConverter(isCached = false).convertSet(input = items)
|
||||
val newBalances = YieldBalanceConverter(isCached = false).convertSet(input = items)
|
||||
|
||||
runtimeStore.update(default = emptyMap()) { saved ->
|
||||
saved.toMutableMap().apply {
|
||||
this[userWalletId] = saved[userWalletId]
|
||||
?.addOrReplace(newBalances) { old, new ->
|
||||
old.integrationId == new.integrationId && old.address == new.address
|
||||
}
|
||||
?: newBalances
|
||||
}
|
||||
}
|
||||
}
|
||||
launch { storeInPersistenceStore(userWalletId = userWalletId, items = items) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun refresh(userWalletId: UserWalletId) {
|
||||
override suspend fun refresh(userWalletId: UserWalletId, addressWithIntegrationIdMap: Map<String, String>) {
|
||||
updateRuntimeStore(userWalletId = userWalletId) { saved ->
|
||||
saved.mapTo(hashSetOf()) {
|
||||
when (it) {
|
||||
is YieldBalance.Data -> it.copy(source = StatusSource.CACHE)
|
||||
is YieldBalance.Empty -> it.copy(source = StatusSource.CACHE)
|
||||
is YieldBalance.Error -> it
|
||||
saved.mapTo(hashSetOf()) { balance ->
|
||||
val refreshIntegrationId = addressWithIntegrationIdMap[balance.address]
|
||||
|
||||
if (balance.integrationId == refreshIntegrationId) {
|
||||
when (balance) {
|
||||
is YieldBalance.Data -> balance.copy(source = StatusSource.CACHE)
|
||||
is YieldBalance.Empty -> balance.copy(source = StatusSource.CACHE)
|
||||
is YieldBalance.Error -> balance
|
||||
}
|
||||
} else {
|
||||
balance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,11 +128,13 @@ internal class DefaultStakingBalanceStore(
|
|||
) {
|
||||
val newBalance = YieldBalanceConverter(isCached = false).convert(value = item)
|
||||
|
||||
val balances = getSyncOrNull(userWalletId)
|
||||
?.addOrReplace(newBalance) { it.integrationId == integrationId && it.address == address }
|
||||
?: setOf(newBalance)
|
||||
|
||||
updateRuntimeStore(userWalletId = userWalletId) { balances }
|
||||
runtimeStore.update(default = emptyMap()) { saved ->
|
||||
saved.toMutableMap().apply {
|
||||
this[userWalletId] = saved[userWalletId]
|
||||
?.addOrReplace(newBalance) { it.integrationId == integrationId && it.address == address }
|
||||
?: setOf(newBalance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateRuntimeStore(
|
||||
|
|
@ -135,7 +151,11 @@ internal class DefaultStakingBalanceStore(
|
|||
private suspend fun storeInPersistenceStore(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
|
||||
persistenceStore.updateData { current ->
|
||||
current.toMutableMap().apply {
|
||||
this[userWalletId.stringValue] = items
|
||||
this[userWalletId.stringValue] = current[userWalletId.stringValue]
|
||||
?.addOrReplace(items = items) { old, new ->
|
||||
old.integrationId == new.integrationId && old.addresses.address == new.addresses.address
|
||||
}
|
||||
?: items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -159,6 +179,8 @@ internal class DefaultStakingBalanceStore(
|
|||
cachedBalances: Set<YieldBalance>,
|
||||
runtimeBalances: Set<YieldBalance>,
|
||||
): Set<YieldBalance> {
|
||||
if (runtimeBalances.isEmpty()) return cachedBalances
|
||||
|
||||
return runtimeBalances
|
||||
.map { runtime ->
|
||||
runtime.takeIf { runtime !is YieldBalance.Error }
|
||||
|
|
|
|||
|
|
@ -27,5 +27,5 @@ interface StakingBalanceStore {
|
|||
/** Store [item] by [userWalletId], [integrationId] and [address] */
|
||||
suspend fun store(userWalletId: UserWalletId, integrationId: String, address: String, item: YieldBalanceWrapperDTO)
|
||||
|
||||
suspend fun refresh(userWalletId: UserWalletId)
|
||||
suspend fun refresh(userWalletId: UserWalletId, addressWithIntegrationIdMap: Map<String, String>)
|
||||
}
|
||||
|
|
@ -676,7 +676,7 @@
|
|||
<string name="scan_card_settings_message">Scanne die Karte oder Ring, um ihre Einstellungen zu ändern. Die Änderungen wirken sich nur auf die von dir gescannte Karte oder Ring aus und haben keine Auswirkungen auf andere mit deiner Wallet verknüpften Geräte.</string>
|
||||
<string name="scan_card_settings_title">Halte deine Karte oder Ring bereit!</string>
|
||||
<string name="security_alert_title">Sicherheitswarnung</string>
|
||||
<string name="seed_warning_no">Nein, habe ich nicht</string>
|
||||
<string name="seed_warning_no">Nein</string>
|
||||
<string name="seed_warning_yes">Ja, leite mich</string>
|
||||
<string name="selling_insufficient_balance_alert_message">Dein Konto verfügt nicht über ausreichend Guthaben, um Kryptowährungen zu verkaufen. Bitte zahle den gewünschten Vermögenswert ein, um fortzufahren.</string>
|
||||
<string name="selling_insufficient_balance_alert_title">Unzureichendes Guthaben</string>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<string name="action_buttons_service_loading_alert_message">これには数秒かかる場合があります。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="action_buttons_service_loading_alert_title">データはまだ読み込まれていません。</string>
|
||||
<string name="action_buttons_something_wrong_alert_message">この操作は現在利用できません。しばらくしてからもう一度お試しいただくか、画面を下にスワイプしてデータを更新してください。</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">ボタンは使用できません</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">アクションは利用できません</string>
|
||||
<string name="action_buttons_swap_choose_token">トークンを選択</string>
|
||||
<string name="action_buttons_swap_empty_search_message">トークンが見つかりませんか?メインページのマーケットセクションに移動し、ポートフォリオに追加してスワップできるようにします。</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">選択したトークンと交換できるトークンがありません。別のトークンを選択してください。</string>
|
||||
|
|
@ -913,8 +913,10 @@
|
|||
<string name="toast_undo">元に戻す</string>
|
||||
<string name="token_button_unavailability_generic_description">この操作は現在利用できません。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="token_button_unavailability_reason_buy_unavailable">%sの買付は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。</string>
|
||||
<string name="token_button_unavailability_reason_custom_token">カスタムトークンでは使用できません。</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_sell">売却できる資金がありません。アカウントに入金して、売却できるようにしてください。</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_send">送金する資金がありません。アカウントに入金して、送金できるようにしてください。</string>
|
||||
<string name="token_button_unavailability_reason_loading">データはまだ読み込まれていません。数秒かかる場合があります。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">%sのスワップは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。</string>
|
||||
<string name="token_button_unavailability_reason_out_of_date_balance">表示残高はキャッシュにより古くなっている可能性があります。</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">%sネットワーク上の保留中の取引が完了すると、資金の売却が可能になります。</string>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<string name="action_buttons_service_loading_alert_message">Это может занять несколько секунд. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="action_buttons_service_loading_alert_title">Данные ещё не загрузились</string>
|
||||
<string name="action_buttons_something_wrong_alert_message">Это действие в данный момент недоступно, пожалуйста, попробуйте позже или обновите данные, сделав свайп экрана вниз.</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">Кнопка недоступна</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">Действие недоступно</string>
|
||||
<string name="action_buttons_swap_choose_token">Выберите токен</string>
|
||||
<string name="action_buttons_swap_empty_search_message">Не нашли свой токен? Перейдите в раздел «Рынок» на главной странице и добавьте его в свой портфель для обмена.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">Нет доступных вариантов для обмена с выбранным токеном, пожалуйста, выберите другой.</string>
|
||||
|
|
@ -694,6 +694,7 @@
|
|||
<string name="scan_card_settings_message">Отсканируйте карту или кольцо, чтобы изменить ее настройки. Изменения затронут только ту карту или кольцо, которые вы отсканировали, и не повлияют на другие устройства, привязанные к вашему кошельку.</string>
|
||||
<string name="scan_card_settings_title">Приготовьте свой Tangem!</string>
|
||||
<string name="security_alert_title">Уведомление безопасности</string>
|
||||
<string name="seed_warning_no">Нет</string>
|
||||
<string name="seed_warning_yes">Да</string>
|
||||
<string name="selling_insufficient_balance_alert_message">На вашем балансе недостаточно средств для продажи криптовалюты. Пожалуйста, пополните нужный актив, чтобы продолжить.</string>
|
||||
<string name="selling_insufficient_balance_alert_title">Недостаточно средств</string>
|
||||
|
|
@ -1113,6 +1114,7 @@
|
|||
<string name="warning_rate_app_title">Нравится Tangem?</string>
|
||||
<string name="warning_receive_blocked_hedera_token_association_required_message">Вам необходимо провести ассоциацию токена для того, чтобы иметь возможность принимать его</string>
|
||||
<string name="warning_rent_fee_title">Необходима плата за аренду сети</string>
|
||||
<string name="warning_seedphrase_action_required_title">Требуется действие</string>
|
||||
<string name="warning_seedphrase_contacted_support">Вы обращались в службу поддержки через приложение в течение 7 дней после создания кошелька? Если обращались или не уверены, нажмите «Да» и следуйте инструкциям.</string>
|
||||
<string name="warning_seedphrase_issue_answer_no">Спасибо! Всё в порядке! Дальнейшие действия не требуются.</string>
|
||||
<string name="warning_seedphrase_issue_answer_yes">Вы будете перенаправлены на официальный сайт Tangem. Пожалуйста, прочитайте и выполните указанные там инструкции.</string>
|
||||
|
|
|
|||
|
|
@ -694,6 +694,8 @@
|
|||
<string name="scan_card_settings_message">Відскануйте картку або кільце, щоб змінити її налаштування. Зміни торкнуться лише тої картки або кільця, які ви відсканували, і не вплинуть на інші пристрої, прив\'язані до вашого гаманця.</string>
|
||||
<string name="scan_card_settings_title">Підготуйте свій Tangem!</string>
|
||||
<string name="security_alert_title">Оповіщення безпеки</string>
|
||||
<string name="seed_warning_no">Ні, не робив</string>
|
||||
<string name="seed_warning_yes">Так</string>
|
||||
<string name="selling_insufficient_balance_alert_message">На вашому балансі недостатньо коштів для продажу криптовалюти. Будь ласка, внесіть бажаний актив, щоб продовжити.</string>
|
||||
<string name="selling_insufficient_balance_alert_title">Недостатній баланс</string>
|
||||
<string name="selling_regional_restriction_alert_message">Продаж криптовалюти у вашому регіоні тимчасово недоступний. Ми активно працюємо над тим, щоб додати цю можливість. Слідкуйте за нашими новинами!</string>
|
||||
|
|
@ -1111,6 +1113,7 @@
|
|||
<string name="warning_rate_app_title">Подобається Tangem?</string>
|
||||
<string name="warning_receive_blocked_hedera_token_association_required_message">Вам необхідно провести асоціацію токена, щоб мати можливість приймати його</string>
|
||||
<string name="warning_rent_fee_title">Необхідна плата за оренду мережі</string>
|
||||
<string name="warning_seedphrase_action_required_title">Потрібна дія</string>
|
||||
<string name="warning_seedphrase_contacted_support">Чи зверталися в службу підтримки через додаток протягом 7 днів після створення гаманця? Якщо зверталися або не впевнені, натисніть «Так» і дотримуйтесь інструкцій.</string>
|
||||
<string name="warning_seedphrase_issue_answer_no">Дякуємо! Все добре! Подальших дій не потрібно.</string>
|
||||
<string name="warning_seedphrase_issue_answer_yes">Ви будете перенаправлені на офіційний сайт Tangem. Будь ласка, прочитайте та виконайте вказані там інструкції.</string>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<string name="action_buttons_service_loading_alert_message">This may take a few seconds. Please try again later.</string>
|
||||
<string name="action_buttons_service_loading_alert_title">The data has not loaded yet.</string>
|
||||
<string name="action_buttons_something_wrong_alert_message">The action is currently unavailable. Please try again later or refresh the data by swiping down on the screen.</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">Button is unavailable</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">Action is unavailable</string>
|
||||
<string name="action_buttons_swap_choose_token">Choose the Token</string>
|
||||
<string name="action_buttons_swap_empty_search_message">Can’t find your token? Go to the Market section on the main page and add it to your portfolio for swapping.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">There are no available tokens to swap with the selected token. Please choose another one.</string>
|
||||
|
|
@ -241,6 +241,7 @@
|
|||
<string name="details_manage_security_long_tap_description">This mechanism protects against proximity attacks on a card or ring. It will enforce a delay between reception and execution of a command.</string>
|
||||
<string name="details_manage_security_passcode">Passcode</string>
|
||||
<string name="details_manage_security_passcode_description">Before executing any command entailing a change of the card state, you will have to enter the passcode.</string>
|
||||
<string name="details_nft_title">NFT</string>
|
||||
<string name="details_referral_title">Referral program</string>
|
||||
<string name="details_row_description_flip_to_hide">Flip your device screen down to quickly hide and show balances</string>
|
||||
<string name="details_row_subtitle_signed_hashes_format">%s hashes</string>
|
||||
|
|
@ -923,8 +924,10 @@
|
|||
<string name="toast_undo">Undo</string>
|
||||
<string name="token_button_unavailability_generic_description">This operation is currently unavailable. Please try again later.</string>
|
||||
<string name="token_button_unavailability_reason_buy_unavailable">Buying %s is not supported by current providers, but we are working to add more options.</string>
|
||||
<string name="token_button_unavailability_reason_custom_token">Action is not available for custom tokens.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_sell">You do not have funds to sell. Top up your account to be able to sell funds from it.</string>
|
||||
<string name="token_button_unavailability_reason_empty_balance_send">You do not have funds to send. Top up your account to be able to send funds from it.</string>
|
||||
<string name="token_button_unavailability_reason_loading">The data has not loaded yet. This may take a few seconds. Please try again later.</string>
|
||||
<string name="token_button_unavailability_reason_not_exchangeable">Swapping %s is not supported by current providers, but we are working to add more options.</string>
|
||||
<string name="token_button_unavailability_reason_out_of_date_balance">The displayed balance might be outdated due to caching.</string>
|
||||
<string name="token_button_unavailability_reason_pending_transaction_sell">Selling funds will be available once the pending transaction(s) on the %s network is complete.</string>
|
||||
|
|
|
|||
|
|
@ -135,6 +135,12 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getYield(yieldId: String): Yield {
|
||||
return withContext(dispatchers.io) {
|
||||
getEnabledYieldsSync().find { it.id == yieldId } ?: error("Staking is unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getActions(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
|
|
@ -396,13 +402,31 @@ internal class DefaultStakingRepository(
|
|||
?: YieldBalance.Error(integrationId, address)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override suspend fun fetchMultiYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (refresh) {
|
||||
stakingBalanceStore.refresh(userWalletId = userWalletId)
|
||||
stakingBalanceStore.refresh(
|
||||
userWalletId = userWalletId,
|
||||
addressWithIntegrationIdMap = cryptoCurrencies
|
||||
.mapNotNull { currency ->
|
||||
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
|
||||
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
|
||||
|
||||
if (integrationId != null) {
|
||||
addresses to integrationId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.flatMap { (addresses, integrationId) ->
|
||||
addresses.map { address -> integrationId to address.value }
|
||||
}
|
||||
.toMap(),
|
||||
)
|
||||
}
|
||||
|
||||
cacheRegistry.invokeOnExpire(
|
||||
|
|
|
|||
|
|
@ -107,9 +107,10 @@ internal class DefaultCurrenciesRepository(
|
|||
)
|
||||
|
||||
val currenciesToAdd = populateCurrenciesWithMissedCoins(
|
||||
currencies = filterAlreadyAddedCurrencies(savedCurrencies.tokens, currencies),
|
||||
)
|
||||
|
||||
currencies = currencies,
|
||||
).let {
|
||||
filterAlreadyAddedCurrencies(savedCurrencies.tokens, it)
|
||||
}
|
||||
val updatedResponse = savedCurrencies.copy(
|
||||
tokens = savedCurrencies.tokens + currenciesToAdd.map(userTokensResponseFactory::createResponseToken),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -35,8 +35,22 @@ internal class DefaultQuotesRepository(
|
|||
private var quotesFetchedForAppCurrency: String? = null
|
||||
private val mutex = Mutex()
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun getQuotesUpdates(currenciesIds: Set<CryptoCurrency.RawID>): Flow<Set<Quote>> {
|
||||
return quotesStore.get(currenciesIds)
|
||||
return appPreferencesStore.getObject<CurrenciesResponse.Currency>(
|
||||
key = PreferencesKeys.SELECTED_APP_CURRENCY_KEY,
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.filterNotNull()
|
||||
.flatMapLatest { appCurrency ->
|
||||
fetchExpiredQuotes(
|
||||
currenciesIds = currenciesIds,
|
||||
appCurrencyId = appCurrency.id,
|
||||
refresh = false,
|
||||
)
|
||||
|
||||
quotesStore.get(currenciesIds)
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +120,7 @@ internal class DefaultQuotesRepository(
|
|||
|
||||
val expiredCurrenciesIds = filterExpiredCurrenciesIds(
|
||||
currenciesIds = currenciesIds,
|
||||
appCurrencyId = appCurrencyId,
|
||||
refresh = refresh || quotesFetchedForAppCurrency != appCurrencyId,
|
||||
)
|
||||
if (expiredCurrenciesIds.isEmpty()) return
|
||||
|
|
@ -136,7 +151,7 @@ internal class DefaultQuotesRepository(
|
|||
onError = { error ->
|
||||
Timber.e(error)
|
||||
|
||||
cacheRegistry.invalidate(rawCurrenciesIds.map { getQuoteCacheKey(it) })
|
||||
cacheRegistry.invalidate(rawCurrenciesIds.map { getQuoteCacheKey(it, appCurrencyId) })
|
||||
quotesStore.storeEmptyQuotes(currenciesIds = rawCurrenciesIds)
|
||||
},
|
||||
)
|
||||
|
|
@ -144,12 +159,13 @@ internal class DefaultQuotesRepository(
|
|||
|
||||
private suspend fun filterExpiredCurrenciesIds(
|
||||
currenciesIds: Set<CryptoCurrency.RawID>,
|
||||
appCurrencyId: String,
|
||||
refresh: Boolean,
|
||||
): Set<CryptoCurrency.RawID> {
|
||||
return currenciesIds.fold(hashSetOf()) { acc, currencyId ->
|
||||
if (currencyId !in acc) {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getQuoteCacheKey(currencyId),
|
||||
key = getQuoteCacheKey(currencyId, appCurrencyId),
|
||||
skipCache = refresh,
|
||||
block = { acc.add(currencyId) },
|
||||
)
|
||||
|
|
@ -158,5 +174,7 @@ internal class DefaultQuotesRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getQuoteCacheKey(rawCurrencyId: CryptoCurrency.RawID): String = "quote_${rawCurrencyId.value}"
|
||||
private fun getQuoteCacheKey(rawCurrencyId: CryptoCurrency.RawID, appCurrencyId: String): String {
|
||||
return "quote_${rawCurrencyId.value}_$appCurrencyId"
|
||||
}
|
||||
}
|
||||
|
|
@ -73,6 +73,7 @@ private fun ScanResponse.hasDerivation(curve: EllipticCurve, derivationPath: Der
|
|||
* @return null if wallet is not multi-currency or total cards count
|
||||
*/
|
||||
fun ScanResponse.getCardsCount(): Int? {
|
||||
if (cardTypesResolver.isTangemTwins()) return 2
|
||||
if (!cardTypesResolver.isMultiwalletAllowed()) return null
|
||||
|
||||
return when (val status = card.backupStatus) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ dependencies {
|
|||
api(projects.domain.core)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.legacy)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import arrow.core.flatten
|
|||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
@ -18,6 +19,7 @@ class SaveManagedTokensUseCase(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val derivationsRepository: DerivationsRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
|
|
@ -50,6 +52,8 @@ class SaveManagedTokensUseCase(
|
|||
existingCurrencies = existingCurrencies,
|
||||
currenciesToAdd = addingCurrencies,
|
||||
)
|
||||
|
||||
refreshUpdatedYieldBalances(userWalletId, existingCurrencies)
|
||||
}
|
||||
|
||||
private suspend fun removeCurrenciesFromWalletManager(
|
||||
|
|
@ -90,6 +94,17 @@ class SaveManagedTokensUseCase(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun refreshUpdatedYieldBalances(
|
||||
userWalletId: UserWalletId,
|
||||
existingCurrencies: List<CryptoCurrency>,
|
||||
) {
|
||||
stakingRepository.fetchMultiYieldBalance(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencies = existingCurrencies,
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the [existingCurrencies] list contains a coin that corresponds
|
||||
* to the given [network].
|
||||
|
|
|
|||
|
|
@ -31,36 +31,40 @@ class SaveMarketTokensUseCase(
|
|||
addedNetworks: Set<TokenMarketInfo.Network>,
|
||||
removedNetworks: Set<TokenMarketInfo.Network>,
|
||||
): Either<Throwable, Unit> = Either.catch {
|
||||
currenciesRepository.removeCurrencies(
|
||||
userWalletId = userWalletId,
|
||||
currencies = removedNetworks.mapNotNull {
|
||||
if (removedNetworks.isNotEmpty()) {
|
||||
currenciesRepository.removeCurrencies(
|
||||
userWalletId = userWalletId,
|
||||
currencies = removedNetworks.mapNotNull {
|
||||
marketsTokenRepository.createCryptoCurrency(
|
||||
userWalletId = userWalletId,
|
||||
token = tokenMarketParams,
|
||||
network = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (addedNetworks.isNotEmpty()) {
|
||||
derivationsRepository.derivePublicKeysByNetworkIds(
|
||||
userWalletId = userWalletId,
|
||||
networkIds = addedNetworks.map { Network.ID(it.networkId) },
|
||||
)
|
||||
|
||||
val addedCurrencies = addedNetworks.mapNotNull {
|
||||
marketsTokenRepository.createCryptoCurrency(
|
||||
userWalletId = userWalletId,
|
||||
token = tokenMarketParams,
|
||||
network = it,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
derivationsRepository.derivePublicKeysByNetworkIds(
|
||||
userWalletId = userWalletId,
|
||||
networkIds = addedNetworks.map { Network.ID(it.networkId) },
|
||||
)
|
||||
currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = addedCurrencies)
|
||||
|
||||
val addedCurrencies = addedNetworks.mapNotNull {
|
||||
marketsTokenRepository.createCryptoCurrency(
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
userWalletId = userWalletId,
|
||||
token = tokenMarketParams,
|
||||
network = it,
|
||||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
|
||||
currenciesRepository.addCurrencies(userWalletId = userWalletId, currencies = addedCurrencies)
|
||||
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
userWalletId = userWalletId,
|
||||
networks = addedCurrencies.map(CryptoCurrency::network).toSet(),
|
||||
refresh = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,4 +20,10 @@ class GetYieldUseCase(
|
|||
.catch { stakingRepository.getYield(cryptoCurrencyId, symbol) }
|
||||
.mapLeft { stakingErrorResolver.resolve(it) }
|
||||
}
|
||||
|
||||
suspend operator fun invoke(yieldId: String): Either<StakingError, Yield> {
|
||||
return Either
|
||||
.catch { stakingRepository.getYield(yieldId) }
|
||||
.mapLeft { stakingErrorResolver.resolve(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,8 @@ interface StakingRepository {
|
|||
|
||||
suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield
|
||||
|
||||
suspend fun getYield(yieldId: String): Yield
|
||||
|
||||
suspend fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): StakingAvailability
|
||||
|
||||
suspend fun getActions(
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.tangem.domain.tokens.repository.QuotesRepository
|
|||
import com.tangem.domain.tokens.utils.CurrencyStatusProxyCreator
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Base operations for working with currency status
|
||||
|
|
@ -28,6 +29,7 @@ import kotlinx.coroutines.flow.*
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LargeClass")
|
||||
abstract class BaseCurrencyStatusOperations(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
|
|
@ -44,6 +46,13 @@ abstract class BaseCurrencyStatusOperations(
|
|||
network: Network,
|
||||
): EitherFlow<Error, Set<NetworkStatus>>
|
||||
|
||||
protected abstract suspend fun fetchComponents(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
currenciesIds: Set<CryptoCurrency.ID>,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit>
|
||||
|
||||
suspend fun getCurrencyStatusFlow(
|
||||
userWalletId: UserWalletId,
|
||||
currencyId: CryptoCurrency.ID,
|
||||
|
|
@ -93,13 +102,26 @@ abstract class BaseCurrencyStatusOperations(
|
|||
|
||||
val yieldBalanceFlow = getYieldBalance(userWalletId = userWalletId, cryptoCurrency = currency)
|
||||
|
||||
return combine(quoteFlow, statusFlow, yieldBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeYieldBalance ->
|
||||
currencyStatusProxyCreator.createCurrencyStatus(
|
||||
currency = currency,
|
||||
maybeQuote = maybeQuote,
|
||||
maybeNetworkStatus = maybeNetworkStatus,
|
||||
maybeYieldBalance = maybeYieldBalance,
|
||||
)
|
||||
return channelFlow {
|
||||
launch {
|
||||
fetchComponents(
|
||||
userWalletId = userWalletId,
|
||||
networks = nonEmptySetOf(currency.network),
|
||||
currenciesIds = nonEmptySetOf(currency.id),
|
||||
currencies = nonEmptyListOf(currency),
|
||||
)
|
||||
}
|
||||
|
||||
combine(quoteFlow, statusFlow, yieldBalanceFlow) { maybeQuote, maybeNetworkStatus, maybeYieldBalance ->
|
||||
currencyStatusProxyCreator.createCurrencyStatus(
|
||||
currency = currency,
|
||||
maybeQuote = maybeQuote,
|
||||
maybeNetworkStatus = maybeNetworkStatus,
|
||||
maybeYieldBalance = maybeYieldBalance,
|
||||
)
|
||||
}
|
||||
.onEach(::send)
|
||||
.launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.domain.tokens.operations
|
||||
|
||||
import arrow.core.*
|
||||
import arrow.core.raise.*
|
||||
import arrow.core.raise.recover
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.core.lce.lce
|
||||
|
|
@ -98,14 +98,14 @@ class CachedCurrenciesStatusesOperations(
|
|||
.launchIn(scope = this)
|
||||
}
|
||||
|
||||
private suspend fun Raise<TokenListError>.fetchComponents(
|
||||
override suspend fun fetchComponents(
|
||||
userWalletId: UserWalletId,
|
||||
networks: NonEmptySet<Network>,
|
||||
currenciesIds: NonEmptySet<CryptoCurrency.ID>,
|
||||
currencies: NonEmptyList<CryptoCurrency>,
|
||||
) = coroutineScope {
|
||||
catch(
|
||||
block = {
|
||||
networks: Set<Network>,
|
||||
currenciesIds: Set<CryptoCurrency.ID>,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> {
|
||||
return coroutineScope {
|
||||
Either.catch {
|
||||
awaitAll(
|
||||
async { networksRepository.fetchNetworkStatuses(userWalletId, networks) },
|
||||
async {
|
||||
|
|
@ -114,11 +114,9 @@ class CachedCurrenciesStatusesOperations(
|
|||
},
|
||||
async { stakingRepository.fetchMultiYieldBalance(userWalletId, currencies) },
|
||||
)
|
||||
},
|
||||
catch = {
|
||||
raise(TokenListError.DataError(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
.map { }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCurrenciesStatuses(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@ class CurrenciesStatusesOperations(
|
|||
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
|
||||
}
|
||||
|
||||
override suspend fun fetchComponents(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
currenciesIds: Set<CryptoCurrency.ID>,
|
||||
currencies: List<CryptoCurrency>,
|
||||
): Either<Throwable, Unit> = Unit.right()
|
||||
|
||||
sealed class Error {
|
||||
|
||||
data object EmptyCurrencies : Error()
|
||||
|
|
|
|||
|
|
@ -39,78 +39,9 @@ class MockStakingRepository : StakingRepository {
|
|||
rewardSchedule = Yield.Metadata.RewardSchedule.DAY,
|
||||
)
|
||||
|
||||
override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield = Yield(
|
||||
id = "1",
|
||||
token = Token(
|
||||
name = "Solana",
|
||||
network = NetworkType.SOLANA,
|
||||
symbol = "SOL",
|
||||
decimals = 18,
|
||||
address = null,
|
||||
coinGeckoId = "solana",
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
tokens = listOf(),
|
||||
args = Yield.Args(
|
||||
enter = Yield.Args.Enter(
|
||||
addresses = Yield.Args.Enter.Addresses(
|
||||
address = AddressArgument(
|
||||
required = false,
|
||||
network = null,
|
||||
minimum = null,
|
||||
maximum = null,
|
||||
),
|
||||
additionalAddresses = mapOf(),
|
||||
),
|
||||
args = mapOf(),
|
||||
),
|
||||
exit = null,
|
||||
),
|
||||
status = Yield.Status(enter = false, exit = null),
|
||||
apy = 1.toBigDecimal(),
|
||||
rewardRate = 2.3,
|
||||
rewardType = Yield.RewardType.APR,
|
||||
metadata = Yield.Metadata(
|
||||
name = "Yield",
|
||||
logoUri = "",
|
||||
description = "",
|
||||
documentation = null,
|
||||
gasFeeToken = Token(
|
||||
name = "Solana",
|
||||
network = NetworkType.SOLANA,
|
||||
symbol = "SOL",
|
||||
decimals = 18,
|
||||
address = null,
|
||||
coinGeckoId = null,
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
token = Token(
|
||||
name = "Solana",
|
||||
network = NetworkType.SOLANA,
|
||||
symbol = "SOL",
|
||||
decimals = 18,
|
||||
address = null,
|
||||
coinGeckoId = null,
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
tokens = listOf(),
|
||||
type = "auto",
|
||||
rewardSchedule = Yield.Metadata.RewardSchedule.DAY,
|
||||
cooldownPeriod = Yield.Metadata.Period(days = 1),
|
||||
warmupPeriod = Yield.Metadata.Period(days = 1),
|
||||
rewardClaiming = Yield.Metadata.RewardClaiming.AUTO,
|
||||
defaultValidator = null,
|
||||
minimumStake = null,
|
||||
supportsMultipleValidators = false,
|
||||
revshare = Yield.Metadata.Enabled(enabled = false),
|
||||
fee = Yield.Metadata.Enabled(enabled = false),
|
||||
),
|
||||
validators = listOf(),
|
||||
isAvailable = false,
|
||||
)
|
||||
override suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield = yield
|
||||
|
||||
override suspend fun getYield(yieldId: String) = yield
|
||||
|
||||
override suspend fun getStakingAvailability(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
@ -245,4 +176,79 @@ class MockStakingRepository : StakingRepository {
|
|||
override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval = StakingApproval.Empty
|
||||
|
||||
override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean = false
|
||||
|
||||
private companion object {
|
||||
val yield = Yield(
|
||||
id = "1",
|
||||
token = Token(
|
||||
name = "Solana",
|
||||
network = NetworkType.SOLANA,
|
||||
symbol = "SOL",
|
||||
decimals = 18,
|
||||
address = null,
|
||||
coinGeckoId = "solana",
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
tokens = listOf(),
|
||||
args = Yield.Args(
|
||||
enter = Yield.Args.Enter(
|
||||
addresses = Yield.Args.Enter.Addresses(
|
||||
address = AddressArgument(
|
||||
required = false,
|
||||
network = null,
|
||||
minimum = null,
|
||||
maximum = null,
|
||||
),
|
||||
additionalAddresses = mapOf(),
|
||||
),
|
||||
args = mapOf(),
|
||||
),
|
||||
exit = null,
|
||||
),
|
||||
status = Yield.Status(enter = false, exit = null),
|
||||
apy = 1.toBigDecimal(),
|
||||
rewardRate = 2.3,
|
||||
rewardType = Yield.RewardType.APR,
|
||||
metadata = Yield.Metadata(
|
||||
name = "Yield",
|
||||
logoUri = "",
|
||||
description = "",
|
||||
documentation = null,
|
||||
gasFeeToken = Token(
|
||||
name = "Solana",
|
||||
network = NetworkType.SOLANA,
|
||||
symbol = "SOL",
|
||||
decimals = 18,
|
||||
address = null,
|
||||
coinGeckoId = null,
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
token = Token(
|
||||
name = "Solana",
|
||||
network = NetworkType.SOLANA,
|
||||
symbol = "SOL",
|
||||
decimals = 18,
|
||||
address = null,
|
||||
coinGeckoId = null,
|
||||
logoURI = null,
|
||||
isPoints = null,
|
||||
),
|
||||
tokens = listOf(),
|
||||
type = "auto",
|
||||
rewardSchedule = Yield.Metadata.RewardSchedule.DAY,
|
||||
cooldownPeriod = Yield.Metadata.Period(days = 1),
|
||||
warmupPeriod = Yield.Metadata.Period(days = 1),
|
||||
rewardClaiming = Yield.Metadata.RewardClaiming.AUTO,
|
||||
defaultValidator = null,
|
||||
minimumStake = null,
|
||||
supportsMultipleValidators = false,
|
||||
revshare = Yield.Metadata.Enabled(enabled = false),
|
||||
fee = Yield.Metadata.Enabled(enabled = false),
|
||||
),
|
||||
validators = listOf(),
|
||||
isAvailable = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -177,7 +177,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
|||
AppRoute.Staking(
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
cryptoCurrencyId = cryptoCurrencyData.status.currency.id,
|
||||
yield = yield,
|
||||
yieldId = yield.id,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.features.staking.api
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
|
|
@ -11,7 +10,7 @@ interface StakingComponent : ComposableContentComponent {
|
|||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrencyId: CryptoCurrency.ID,
|
||||
val yield: Yield,
|
||||
val yieldId: String,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, StakingComponent>
|
||||
|
|
|
|||
|
|
@ -26,10 +26,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
|
|||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.staking.GetActionsUseCase
|
||||
import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase
|
||||
import com.tangem.domain.staking.IsAnyTokenStakedUseCase
|
||||
import com.tangem.domain.staking.IsApproveNeededUseCase
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.analytics.StakeScreenSource
|
||||
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
|
||||
import com.tangem.domain.staking.model.StakingApproval
|
||||
|
|
@ -79,6 +76,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
|
@ -116,6 +114,7 @@ internal class StakingModel @Inject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val getActionsUseCase: GetActionsUseCase,
|
||||
private val getYieldUseCase: GetYieldUseCase,
|
||||
private val paramsInterceptorHolder: ParamsInterceptorHolder,
|
||||
private val shareManager: ShareManager,
|
||||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
|
|
@ -136,7 +135,11 @@ internal class StakingModel @Inject constructor(
|
|||
|
||||
private val cryptoCurrencyId: CryptoCurrency.ID = params.cryptoCurrencyId
|
||||
private val userWalletId: UserWalletId = params.userWalletId
|
||||
private val yield: Yield = params.yield
|
||||
private val yield: Yield = runBlocking {
|
||||
getYieldUseCase(params.yieldId).getOrElse {
|
||||
error("yield must be not null")
|
||||
}
|
||||
}
|
||||
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull()
|
||||
private var processingActions: List<StakingAction> = emptyList()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.tangem.common.routing.AppRouter
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import javax.inject.Inject
|
||||
|
|
@ -38,12 +37,12 @@ internal class DefaultTokenDetailsRouter @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yield: Yield) {
|
||||
override fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yieldId: String) {
|
||||
router.push(
|
||||
AppRoute.Staking(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
yield = yield,
|
||||
yieldId = yieldId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.feature.tokendetails.presentation.router
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
|
|
@ -17,7 +16,7 @@ internal interface InnerTokenDetailsRouter {
|
|||
|
||||
fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
|
||||
|
||||
fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yield: Yield)
|
||||
fun openStaking(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, yieldId: String)
|
||||
|
||||
fun openOnrampSuccess(externalTxId: String)
|
||||
}
|
||||
|
|
@ -971,7 +971,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
error("Staking is unavailable for ${cryptoCurrency.name}")
|
||||
}
|
||||
|
||||
router.openStaking(userWalletId, cryptoCurrency, yield)
|
||||
router.openStaking(userWalletId, cryptoCurrency, yield.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
AppRoute.Staking(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
yield = yield ?: return@launch,
|
||||
yieldId = yield?.id ?: return@launch,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
|
|
@ -42,6 +43,8 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
) { maybePrimaryCurrencyStatus, isReadyToShowRating, isNeedToBackup, userWallets ->
|
||||
readyForRateAppNotification = true
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(maybePrimaryCurrencyStatus)
|
||||
|
||||
addCriticalNotifications(
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
)
|
||||
|
|
@ -68,6 +71,18 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addUsedOutdatedDataNotification(
|
||||
maybePrimaryCurrencyStatus: Either<CurrencyStatusError, CryptoCurrencyStatus>,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.UsedOutdatedData,
|
||||
condition = maybePrimaryCurrencyStatus.fold(
|
||||
ifLeft = { false },
|
||||
ifRight = { it.value.source == StatusSource.ONLY_CACHE },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
|
||||
addIf(
|
||||
element = WalletNotification.Critical.DevCard,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
when (action) {
|
||||
is TokenActionsState.ActionState.Buy -> {
|
||||
WalletManageButton.Buy(
|
||||
enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
||||
enabled = true,
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onBuyClick(
|
||||
|
|
@ -56,7 +56,7 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
}
|
||||
is TokenActionsState.ActionState.Receive -> {
|
||||
WalletManageButton.Receive(
|
||||
enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
||||
enabled = true,
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onReceiveClick(cryptoCurrencyStatus = cryptoCurrencyStatus)
|
||||
|
|
@ -68,7 +68,7 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
}
|
||||
is TokenActionsState.ActionState.Sell -> {
|
||||
WalletManageButton.Sell(
|
||||
enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
||||
enabled = true,
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onSellClick(
|
||||
|
|
@ -80,7 +80,7 @@ internal class SetCryptoCurrencyActionsTransformer(
|
|||
}
|
||||
is TokenActionsState.ActionState.Send -> {
|
||||
WalletManageButton.Send(
|
||||
enabled = action.unavailabilityReason == ScenarioUnavailabilityReason.None,
|
||||
enabled = true,
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = {
|
||||
clickIntents.onSendClick(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue