Updated on 2026-08-14
This commit is contained in:
commit
85423227c4
390 changed files with 11140 additions and 1722 deletions
|
|
@ -74,6 +74,7 @@ dependencies {
|
||||||
implementation(projects.domain.walletConnect)
|
implementation(projects.domain.walletConnect)
|
||||||
implementation(projects.domain.markets)
|
implementation(projects.domain.markets)
|
||||||
implementation(projects.domain.manageTokens)
|
implementation(projects.domain.manageTokens)
|
||||||
|
implementation(projects.domain.onramp)
|
||||||
|
|
||||||
implementation(projects.common)
|
implementation(projects.common)
|
||||||
implementation(projects.common.routing)
|
implementation(projects.common.routing)
|
||||||
|
|
@ -112,6 +113,7 @@ dependencies {
|
||||||
implementation(projects.data.walletConnect)
|
implementation(projects.data.walletConnect)
|
||||||
implementation(projects.data.markets)
|
implementation(projects.data.markets)
|
||||||
implementation(projects.data.manageTokens)
|
implementation(projects.data.manageTokens)
|
||||||
|
implementation(projects.data.onramp)
|
||||||
|
|
||||||
/** Features */
|
/** Features */
|
||||||
implementation(projects.features.onboarding)
|
implementation(projects.features.onboarding)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit adbdabe422b0513640d1750f276298683d342f61
|
Subproject commit cce49feacb18dc9ad7cc5da62d58f1b3338a1057
|
||||||
|
|
@ -749,6 +749,16 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "chiliz",
|
||||||
|
"name": "Chiliz",
|
||||||
|
"symbol": "CHZ",
|
||||||
|
"networks": [
|
||||||
|
{
|
||||||
|
"networkId": "chiliz/test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "casper-network",
|
"id": "casper-network",
|
||||||
"name": "Casper",
|
"name": "Casper",
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||||
|
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||||
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
|
||||||
import com.tangem.features.send.api.navigation.SendRouter
|
import com.tangem.features.send.api.navigation.SendRouter
|
||||||
|
|
@ -59,6 +60,7 @@ import com.tangem.features.staking.api.navigation.StakingRouter
|
||||||
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
||||||
import com.tangem.features.wallet.navigation.WalletRouter
|
import com.tangem.features.wallet.navigation.WalletRouter
|
||||||
import com.tangem.operations.backup.BackupService
|
import com.tangem.operations.backup.BackupService
|
||||||
|
import com.tangem.sdk.api.BackupServiceHolder
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
import com.tangem.sdk.extensions.init
|
import com.tangem.sdk.extensions.init
|
||||||
import com.tangem.tap.common.ActivityResultCallbackHolder
|
import com.tangem.tap.common.ActivityResultCallbackHolder
|
||||||
|
|
@ -185,6 +187,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase
|
lateinit var shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var backupServiceHolder: BackupServiceHolder
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var onboardingV2FeatureToggles: OnboardingV2FeatureToggles
|
||||||
|
|
||||||
internal val viewModel: MainViewModel by viewModels()
|
internal val viewModel: MainViewModel by viewModels()
|
||||||
|
|
||||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||||
|
|
@ -294,7 +302,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
||||||
cardSdkOwner.register(activity = this)
|
cardSdkOwner.register(activity = this)
|
||||||
tangemSdkManager = injectedTangemSdkManager
|
tangemSdkManager = injectedTangemSdkManager
|
||||||
appStateHolder.tangemSdkManager = tangemSdkManager
|
appStateHolder.tangemSdkManager = tangemSdkManager
|
||||||
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
|
|
||||||
|
if (onboardingV2FeatureToggles.isOnboardingV2Enabled) {
|
||||||
|
backupServiceHolder.createAndSetService(cardSdkConfigRepository.sdk, this)
|
||||||
|
backupService = backupServiceHolder.backupService.get()!! // will be deleted eventually
|
||||||
|
} else {
|
||||||
|
backupService = BackupService.init(cardSdkConfigRepository.sdk, this)
|
||||||
|
}
|
||||||
|
|
||||||
lockUserWalletsTimer = LockUserWalletsTimer(
|
lockUserWalletsTimer = LockUserWalletsTimer(
|
||||||
owner = this,
|
owner = this,
|
||||||
settingsRepository = settingsRepository,
|
settingsRepository = settingsRepository,
|
||||||
|
|
@ -550,6 +565,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO add onboarding v2 handling
|
||||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,11 @@ class DialogManager : StoreSubscriber<GlobalState> {
|
||||||
message = state.dialog.error.message,
|
message = state.dialog.error.message,
|
||||||
context = context,
|
context = context,
|
||||||
)
|
)
|
||||||
|
is WalletConnectDialog.UnsupportedWcVersion -> SimpleAlertDialog.create(
|
||||||
|
titleRes = R.string.common_error,
|
||||||
|
messageRes = R.string.unsupported_wc_version,
|
||||||
|
context = context,
|
||||||
|
)
|
||||||
is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context)
|
is BackupDialog.AttestationFailed -> AttestationFailedDialog.create(context)
|
||||||
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context)
|
||||||
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context)
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,16 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
||||||
}
|
}
|
||||||
val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card }
|
val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card }
|
||||||
|
|
||||||
|
val buyService = makeBuyExchangeService(config)
|
||||||
|
val sellService = makeSellExchangeService(config)
|
||||||
val exchangeManager = CurrencyExchangeManager(
|
val exchangeManager = CurrencyExchangeManager(
|
||||||
buyService = makeBuyExchangeService(config),
|
buyService = buyService,
|
||||||
sellService = makeSellExchangeService(config),
|
sellService = sellService,
|
||||||
primaryRules = CardExchangeRules(cardProvider),
|
primaryRules = CardExchangeRules(cardProvider),
|
||||||
)
|
)
|
||||||
// TODO: for refactoring (after remove old design refactor CurrencyExchangeManager and use 1 instance)
|
// TODO: for refactoring (after remove old design refactor CurrencyExchangeManager and use 1 instance)
|
||||||
|
store.inject(DaggerGraphState::appStateHolder).buyService = buyService
|
||||||
|
store.inject(DaggerGraphState::appStateHolder).sellService = sellService
|
||||||
store.inject(DaggerGraphState::appStateHolder).exchangeService = exchangeManager
|
store.inject(DaggerGraphState::appStateHolder).exchangeService = exchangeManager
|
||||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
|
store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
|
||||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
|
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.tap.di
|
package com.tangem.tap.di
|
||||||
|
|
||||||
|
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
|
||||||
import com.tangem.domain.card.ScanCardUseCase
|
import com.tangem.domain.card.ScanCardUseCase
|
||||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||||
import com.tangem.domain.exchange.RampStateManager
|
import com.tangem.domain.exchange.RampStateManager
|
||||||
|
|
@ -10,6 +11,8 @@ import com.tangem.sdk.api.TangemSdkManager
|
||||||
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
|
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
|
||||||
import com.tangem.tap.network.exchangeServices.DefaultRampManager
|
import com.tangem.tap.network.exchangeServices.DefaultRampManager
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
|
import com.tangem.utils.Provider
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -39,8 +42,18 @@ internal object ActivityModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideDefaultRampManager(appStateHolder: AppStateHolder): RampStateManager {
|
fun provideDefaultRampManager(
|
||||||
return DefaultRampManager(appStateHolder.exchangeService)
|
appStateHolder: AppStateHolder,
|
||||||
|
swapServiceLoader: SwapServiceLoader,
|
||||||
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
): RampStateManager {
|
||||||
|
return DefaultRampManager(
|
||||||
|
exchangeService = appStateHolder.exchangeService,
|
||||||
|
buyService = Provider { requireNotNull(appStateHolder.buyService) },
|
||||||
|
sellService = Provider { requireNotNull(appStateHolder.sellService) },
|
||||||
|
swapServiceLoader = swapServiceLoader,
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
17
app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt
Normal file
17
app/src/main/java/com/tangem/tap/di/TangemSdkModule.kt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tangem.tap.di
|
||||||
|
|
||||||
|
import com.tangem.sdk.api.BackupServiceHolder
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object TangemSdkModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideBackupServiceHolder(): BackupServiceHolder = BackupServiceHolder()
|
||||||
|
}
|
||||||
105
app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt
Normal file
105
app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.domain.onramp.*
|
||||||
|
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||||
|
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||||
|
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object OnrampDomainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetOnrampCurrenciesUseCase(onrampRepository: OnrampRepository): GetOnrampCurrenciesUseCase {
|
||||||
|
return GetOnrampCurrenciesUseCase(onrampRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideOnrampSaveDefaultCurrencyUseCase(onrampRepository: OnrampRepository): OnrampSaveDefaultCurrencyUseCase {
|
||||||
|
return OnrampSaveDefaultCurrencyUseCase(onrampRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetOnrampCountriesUseCase(onrampRepository: OnrampRepository): GetOnrampCountriesUseCase {
|
||||||
|
return GetOnrampCountriesUseCase(onrampRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetOnrampCountryUseCase(onrampRepository: OnrampRepository): GetOnrampCountryUseCase {
|
||||||
|
return GetOnrampCountryUseCase(onrampRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideOnrampSaveDefaultCountryUseCase(onrampRepository: OnrampRepository): OnrampSaveDefaultCountryUseCase {
|
||||||
|
return OnrampSaveDefaultCountryUseCase(onrampRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideCheckOnrampAvailabilityUseCase(onrampRepository: OnrampRepository): CheckOnrampAvailabilityUseCase {
|
||||||
|
return CheckOnrampAvailabilityUseCase(onrampRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetOnrampStatusUseCase(
|
||||||
|
onrampRepository: OnrampRepository,
|
||||||
|
onrampErrorResolver: OnrampErrorResolver,
|
||||||
|
): GetOnrampStatusUseCase {
|
||||||
|
return GetOnrampStatusUseCase(onrampRepository, onrampErrorResolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetOnrampCurrencyUseCase(onrampRepository: OnrampRepository): GetOnrampCurrencyUseCase {
|
||||||
|
return GetOnrampCurrencyUseCase(onrampRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetOnrampTransactionsUseCase(
|
||||||
|
onrampTransactionRepository: OnrampTransactionRepository,
|
||||||
|
): GetOnrampTransactionsUseCase {
|
||||||
|
return GetOnrampTransactionsUseCase(onrampTransactionRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetOnrampTransactionUseCase(
|
||||||
|
onrampTransactionRepository: OnrampTransactionRepository,
|
||||||
|
): GetOnrampTransactionUseCase {
|
||||||
|
return GetOnrampTransactionUseCase(onrampTransactionRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideOnrampRemoveTransactionUseCase(
|
||||||
|
onrampTransactionRepository: OnrampTransactionRepository,
|
||||||
|
): OnrampRemoveTransactionUseCase {
|
||||||
|
return OnrampRemoveTransactionUseCase(onrampTransactionRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideOnrampSaveTransactionUseCase(
|
||||||
|
onrampTransactionRepository: OnrampTransactionRepository,
|
||||||
|
): OnrampSaveTransactionUseCase {
|
||||||
|
return OnrampSaveTransactionUseCase(onrampTransactionRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetOnrampPaymentMethodsUseCase(onrampRepository: OnrampRepository): GetOnrampPaymentMethodsUseCase {
|
||||||
|
return GetOnrampPaymentMethodsUseCase(onrampRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -133,14 +133,6 @@ internal object SettingsDomainModule {
|
||||||
return ShouldShowRingPromoUseCase(promoSettingsRepository)
|
return ShouldShowRingPromoUseCase(promoSettingsRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideShouldShowTravalaPromoWalletUseCase(
|
|
||||||
promoSettingsRepository: PromoSettingsRepository,
|
|
||||||
): ShouldShowTravalaPromoWalletUseCase {
|
|
||||||
return ShouldShowTravalaPromoWalletUseCase(promoSettingsRepository)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideShouldShowSwapPromoTokenUseCase(
|
fun provideShouldShowSwapPromoTokenUseCase(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.feature.swap.domain.GetAvailablePairsUseCase
|
||||||
|
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object SwapDomainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetAvailablePairsUseCase(swapRepository: SwapRepository): GetAvailablePairsUseCase {
|
||||||
|
return GetAvailablePairsUseCase(swapRepository = swapRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ import com.tangem.domain.tokens.*
|
||||||
import com.tangem.domain.tokens.repository.*
|
import com.tangem.domain.tokens.repository.*
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -119,26 +118,26 @@ internal object TokensDomainModule {
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
networksRepository: NetworksRepository,
|
networksRepository: NetworksRepository,
|
||||||
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
|
||||||
swapRepository: SwapRepository,
|
swapRepository: SwapRepository,
|
||||||
currencyChecksRepository: CurrencyChecksRepository,
|
currencyChecksRepository: CurrencyChecksRepository,
|
||||||
showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
|
showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
|
||||||
promoRepository: PromoRepository,
|
promoRepository: PromoRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
rampStateManager: RampStateManager,
|
||||||
): GetCurrencyWarningsUseCase {
|
): GetCurrencyWarningsUseCase {
|
||||||
return GetCurrencyWarningsUseCase(
|
return GetCurrencyWarningsUseCase(
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
networksRepository = networksRepository,
|
networksRepository = networksRepository,
|
||||||
marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
|
|
||||||
currencyChecksRepository = currencyChecksRepository,
|
currencyChecksRepository = currencyChecksRepository,
|
||||||
swapRepository = swapRepository,
|
swapRepository = swapRepository,
|
||||||
showSwapPromoTokenUseCase = showSwapPromoTokenUseCase,
|
showSwapPromoTokenUseCase = showSwapPromoTokenUseCase,
|
||||||
promoRepository = promoRepository,
|
promoRepository = promoRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
|
rampStateManager = rampStateManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,23 +215,19 @@ internal object TokensDomainModule {
|
||||||
fun provideGetCryptoCurrencyActionsUseCase(
|
fun provideGetCryptoCurrencyActionsUseCase(
|
||||||
rampStateManager: RampStateManager,
|
rampStateManager: RampStateManager,
|
||||||
walletManagersFacade: WalletManagersFacade,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
quotesRepository: QuotesRepository,
|
quotesRepository: QuotesRepository,
|
||||||
networksRepository: NetworksRepository,
|
networksRepository: NetworksRepository,
|
||||||
stakingRepository: StakingRepository,
|
stakingRepository: StakingRepository,
|
||||||
stakingFeatureToggles: StakingFeatureToggles,
|
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): GetCryptoCurrencyActionsUseCase {
|
): GetCryptoCurrencyActionsUseCase {
|
||||||
return GetCryptoCurrencyActionsUseCase(
|
return GetCryptoCurrencyActionsUseCase(
|
||||||
rampManager = rampStateManager,
|
rampManager = rampStateManager,
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
|
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
quotesRepository = quotesRepository,
|
quotesRepository = quotesRepository,
|
||||||
networksRepository = networksRepository,
|
networksRepository = networksRepository,
|
||||||
stakingRepository = stakingRepository,
|
stakingRepository = stakingRepository,
|
||||||
stakingFeatureToggles = stakingFeatureToggles,
|
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,5 +37,7 @@ sealed class WalletConnectAction : Action {
|
||||||
data class PerformRequestedAction(val sessionRequest: WcPreparedRequest) : WalletConnectAction()
|
data class PerformRequestedAction(val sessionRequest: WcPreparedRequest) : WalletConnectAction()
|
||||||
|
|
||||||
data class PairConnectErrorAction(val throwable: Throwable) : WalletConnectAction()
|
data class PairConnectErrorAction(val throwable: Throwable) : WalletConnectAction()
|
||||||
|
|
||||||
|
data object UnsupportedDappRequest : WalletConnectAction()
|
||||||
//endregion WalletConnect 2.0
|
//endregion WalletConnect 2.0
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +78,12 @@ class WalletConnectMiddleware {
|
||||||
val index = action.wcUri.indexOf("@")
|
val index = action.wcUri.indexOf("@")
|
||||||
when (action.wcUri[index + 1]) {
|
when (action.wcUri[index + 1]) {
|
||||||
'2' -> walletConnectRepository.pair(uri = action.wcUri)
|
'2' -> walletConnectRepository.pair(uri = action.wcUri)
|
||||||
|
'1' -> {
|
||||||
|
store.dispatchOnMain(WalletConnectAction.UnsupportedDappRequest)
|
||||||
|
store.dispatchOnMain(
|
||||||
|
GlobalAction.ShowDialog(WalletConnectDialog.UnsupportedWcVersion),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is WalletConnectAction.RejectRequest -> {
|
is WalletConnectAction.RejectRequest -> {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ object WalletConnectReducer {
|
||||||
is WalletConnectAction.SessionEstablished,
|
is WalletConnectAction.SessionEstablished,
|
||||||
is WalletConnectAction.SessionRejected,
|
is WalletConnectAction.SessionRejected,
|
||||||
is WalletConnectAction.PairConnectErrorAction,
|
is WalletConnectAction.PairConnectErrorAction,
|
||||||
|
is WalletConnectAction.UnsupportedDappRequest,
|
||||||
-> state.copy(loading = false)
|
-> state.copy(loading = false)
|
||||||
is WalletConnectAction.SessionListUpdated -> state.copy(
|
is WalletConnectAction.SessionListUpdated -> state.copy(
|
||||||
wc2Sessions = action.sessions,
|
wc2Sessions = action.sessions,
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ data class WalletForSession(
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class WalletConnectDialog : StateDialog {
|
sealed class WalletConnectDialog : StateDialog {
|
||||||
|
data object UnsupportedWcVersion : WalletConnectDialog()
|
||||||
data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog()
|
data class ClipboardOrScanQr(val clipboardUri: String) : WalletConnectDialog()
|
||||||
data object UnsupportedCard : WalletConnectDialog()
|
data object UnsupportedCard : WalletConnectDialog()
|
||||||
data class UnsupportedNetwork(val networks: List<String>? = null) : WalletConnectDialog()
|
data class UnsupportedNetwork(val networks: List<String>? = null) : WalletConnectDialog()
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
||||||
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
|
||||||
import com.tangem.tap.common.extensions.setContext
|
import com.tangem.tap.common.extensions.setContext
|
||||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||||
import com.tangem.tap.features.main.model.MainScreenState
|
import com.tangem.tap.features.main.model.MainScreenState
|
||||||
|
|
@ -40,7 +39,6 @@ internal class MainViewModel @Inject constructor(
|
||||||
private val blockchainSDKFactory: BlockchainSDKFactory,
|
private val blockchainSDKFactory: BlockchainSDKFactory,
|
||||||
private val userWalletsListManager: UserWalletsListManager,
|
private val userWalletsListManager: UserWalletsListManager,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
stakingFeatureToggles: StakingFeatureToggles,
|
|
||||||
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
||||||
private val apiConfigsManager: ApiConfigsManager,
|
private val apiConfigsManager: ApiConfigsManager,
|
||||||
homeFeatureToggles: HomeFeatureToggles,
|
homeFeatureToggles: HomeFeatureToggles,
|
||||||
|
|
@ -78,9 +76,7 @@ internal class MainViewModel @Inject constructor(
|
||||||
displayBalancesHidingStatusToast()
|
displayBalancesHidingStatusToast()
|
||||||
displayHiddenBalancesModalNotification()
|
displayHiddenBalancesModalNotification()
|
||||||
|
|
||||||
if (stakingFeatureToggles.isStakingEnabled) {
|
fetchStakingTokens()
|
||||||
fetchStakingTokens()
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteDeprecatedLogsUseCase()
|
deleteDeprecatedLogsUseCase()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||||
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||||
import com.tangem.tap.common.analytics.events.Onboarding
|
import com.tangem.tap.common.analytics.events.Onboarding
|
||||||
|
|
@ -67,7 +68,10 @@ object OnboardingHelper {
|
||||||
|
|
||||||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||||
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled) {
|
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled) {
|
||||||
return AppRoute.Onboarding(scanResponse)
|
return AppRoute.Onboarding(
|
||||||
|
scanResponse = scanResponse,
|
||||||
|
startFromBackup = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (val type = scanResponse.productType) {
|
return when (val type = scanResponse.productType) {
|
||||||
|
|
@ -88,6 +92,7 @@ object OnboardingHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveWallet(
|
fun saveWallet(
|
||||||
|
alreadyCreatedWallet: UserWallet?,
|
||||||
scanResponse: ScanResponse,
|
scanResponse: ScanResponse,
|
||||||
accessCode: String? = null,
|
accessCode: String? = null,
|
||||||
backupCardsIds: List<String>? = null,
|
backupCardsIds: List<String>? = null,
|
||||||
|
|
@ -117,7 +122,7 @@ object OnboardingHelper {
|
||||||
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
|
||||||
// then open save wallet screen
|
// then open save wallet screen
|
||||||
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
||||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||||
|
|
||||||
delay(timeMillis = 1_200)
|
delay(timeMillis = 1_200)
|
||||||
|
|
||||||
|
|
@ -131,7 +136,7 @@ object OnboardingHelper {
|
||||||
}
|
}
|
||||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||||
else -> {
|
else -> {
|
||||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,9 +247,10 @@ object OnboardingHelper {
|
||||||
scanResponse: ScanResponse,
|
scanResponse: ScanResponse,
|
||||||
backupCardsIds: List<String>?,
|
backupCardsIds: List<String>?,
|
||||||
hasBackupError: Boolean,
|
hasBackupError: Boolean,
|
||||||
|
alreadyCreatedWallet: UserWallet? = null,
|
||||||
) {
|
) {
|
||||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||||
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
val userWallet = alreadyCreatedWallet ?: UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||||
.hasBackupError(hasBackupError)
|
.hasBackupError(hasBackupError)
|
||||||
.backupCardsIds(backupCardsIds?.toSet())
|
.backupCardsIds(backupCardsIds?.toSet())
|
||||||
.build()
|
.build()
|
||||||
|
|
|
||||||
|
|
@ -197,11 +197,11 @@ private fun handleWalletAction(action: Action) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFinishBackup(scanResponse: ScanResponse) {
|
private fun handleFinishBackup(scanResponse: ScanResponse, userWallet: UserWallet? = null) {
|
||||||
val backupState = store.state.onboardingWalletState.backupState
|
val backupState = store.state.onboardingWalletState.backupState
|
||||||
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
|
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
|
||||||
|
|
||||||
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
val userWalletId = userWallet?.walletId ?: UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||||
if (backupState.hasRing && userWalletId != null) {
|
if (backupState.hasRing && userWalletId != null) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
store.inject(DaggerGraphState::walletsRepository).setHasWalletsWithRing(userWalletId = userWalletId)
|
store.inject(DaggerGraphState::walletsRepository).setHasWalletsWithRing(userWalletId = userWalletId)
|
||||||
|
|
@ -209,6 +209,7 @@ private fun handleFinishBackup(scanResponse: ScanResponse) {
|
||||||
}
|
}
|
||||||
|
|
||||||
OnboardingHelper.saveWallet(
|
OnboardingHelper.saveWallet(
|
||||||
|
alreadyCreatedWallet = userWallet,
|
||||||
scanResponse = updatedScanResponse,
|
scanResponse = updatedScanResponse,
|
||||||
accessCode = backupState.accessCode,
|
accessCode = backupState.accessCode,
|
||||||
backupCardsIds = backupState.backupCardIds,
|
backupCardsIds = backupState.backupCardIds,
|
||||||
|
|
@ -679,7 +680,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
||||||
Analytics.send(Onboarding.Finished())
|
Analytics.send(Onboarding.Finished())
|
||||||
|
|
||||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds)
|
store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds)
|
||||||
handleFinishBackup(requireNotNull(scanResponse))
|
handleFinishBackup(requireNotNull(scanResponse), userWallet)
|
||||||
delay(1000)
|
delay(1000)
|
||||||
store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet?.walletId))
|
store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet?.walletId))
|
||||||
}
|
}
|
||||||
|
|
@ -740,6 +741,7 @@ private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: Ba
|
||||||
return requireNotNull(
|
return requireNotNull(
|
||||||
value = UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
value = UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||||
.backupCardsIds(backupState.backupCardIds.toSet())
|
.backupCardsIds(backupState.backupCardIds.toSet())
|
||||||
|
.hasBackupError(backupState.hasBackupError)
|
||||||
.build(),
|
.build(),
|
||||||
lazyMessage = { "User wallet not created" },
|
lazyMessage = { "User wallet not created" },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -55,11 +55,15 @@ object TradeCryptoMiddleware {
|
||||||
|
|
||||||
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
||||||
val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled
|
val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled
|
||||||
if (isOnrampEnabled) proceedWithOnramp() else proceedWithLegacyBuyAction(state, action)
|
if (isOnrampEnabled) {
|
||||||
|
proceedWithOnramp(action.cryptoCurrencyStatus.currency)
|
||||||
|
} else {
|
||||||
|
proceedWithLegacyBuyAction(state, action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun proceedWithOnramp() {
|
private fun proceedWithOnramp(cryptoCurrency: CryptoCurrency) {
|
||||||
store.dispatchNavigationAction { push(AppRoute.Onramp) }
|
store.dispatchNavigationAction { push(AppRoute.Onramp(cryptoCurrency)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||||
import com.tangem.blockchain.extensions.Result
|
import com.tangem.blockchain.extensions.Result
|
||||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||||
|
import com.tangem.domain.core.utils.lceContent
|
||||||
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
|
@ -19,6 +21,8 @@ import com.tangem.tap.domain.model.Currency
|
||||||
import com.tangem.tap.features.demo.isDemoCard
|
import com.tangem.tap.features.demo.isDemoCard
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -30,9 +34,19 @@ class CurrencyExchangeManager(
|
||||||
private val primaryRules: ExchangeRules,
|
private val primaryRules: ExchangeRules,
|
||||||
) : ExchangeService {
|
) : ExchangeService {
|
||||||
|
|
||||||
|
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||||
|
get() = _initializationStatus
|
||||||
|
|
||||||
|
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
|
||||||
|
MutableStateFlow(value = lceLoading())
|
||||||
|
|
||||||
override suspend fun update() {
|
override suspend fun update() {
|
||||||
|
_initializationStatus.value = lceLoading()
|
||||||
|
|
||||||
buyService.update()
|
buyService.update()
|
||||||
sellService.update()
|
sellService.update()
|
||||||
|
|
||||||
|
_initializationStatus.value = lceContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isBuyAllowed(): Boolean = primaryRules.isBuyAllowed() && buyService.isBuyAllowed()
|
override fun isBuyAllowed(): Boolean = primaryRules.isBuyAllowed() && buyService.isBuyAllowed()
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,29 @@
|
||||||
package com.tangem.tap.network.exchangeServices
|
package com.tangem.tap.network.exchangeServices
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
|
||||||
|
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
|
||||||
import com.tangem.domain.exchange.RampStateManager
|
import com.tangem.domain.exchange.RampStateManager
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import com.tangem.utils.Provider
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class DefaultRampManager(private val exchangeService: ExchangeService?) : RampStateManager {
|
internal class DefaultRampManager(
|
||||||
|
private val exchangeService: ExchangeService?,
|
||||||
|
private val buyService: Provider<ExchangeService>,
|
||||||
|
private val sellService: Provider<ExchangeService>,
|
||||||
|
private val swapServiceLoader: SwapServiceLoader,
|
||||||
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
) : RampStateManager {
|
||||||
|
|
||||||
private val cryptoCurrencyConverter = CryptoCurrencyConverter()
|
private val cryptoCurrencyConverter = CryptoCurrencyConverter()
|
||||||
|
|
||||||
override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean {
|
override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean {
|
||||||
return exchangeService?.availableForBuy(
|
return exchangeService?.availableForBuy(
|
||||||
scanResponse,
|
scanResponse = scanResponse,
|
||||||
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
||||||
) ?: false
|
) ?: false
|
||||||
}
|
}
|
||||||
|
|
@ -19,4 +33,46 @@ class DefaultRampManager(private val exchangeService: ExchangeService?) : RampSt
|
||||||
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
||||||
) ?: false
|
) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
|
||||||
|
return getExchangeableFlag(userWalletId, cryptoCurrency) && !cryptoCurrency.isCustom
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBuyInitializationStatus(): Flow<ExchangeServiceInitializationStatus> {
|
||||||
|
return buyService.invoke().initializationStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchBuyServiceData() {
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
buyService.invoke().update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSellInitializationStatus(): Flow<ExchangeServiceInitializationStatus> {
|
||||||
|
return sellService.invoke().initializationStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchSellServiceData() {
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
sellService.invoke().update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSwapInitializationStatus(userWalletId: UserWalletId): Flow<ExchangeServiceInitializationStatus> {
|
||||||
|
return swapServiceLoader.getInitializationStatus(userWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getExchangeableFlag(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
|
||||||
|
return withContext(dispatchers.io) {
|
||||||
|
val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress
|
||||||
|
?: EMPTY_CONTRACT_ADDRESS_VALUE
|
||||||
|
|
||||||
|
val asset = swapServiceLoader.getInitializationStatus(userWalletId).value.getOrNull()?.find {
|
||||||
|
it.network == cryptoCurrency.network.backendId &&
|
||||||
|
it.contractAddress.equals(contractAddress, ignoreCase = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
asset?.exchangeAvailable ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
package com.tangem.tap.network.exchangeServices
|
package com.tangem.tap.network.exchangeServices
|
||||||
|
|
||||||
|
import com.tangem.domain.core.lce.Lce
|
||||||
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.tap.domain.model.Currency
|
import com.tangem.tap.domain.model.Currency
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
typealias ExchangeServiceInitializationStatus = Lce<Throwable, Any>
|
||||||
|
|
||||||
interface Exchanger {
|
interface Exchanger {
|
||||||
fun isBuyAllowed(): Boolean
|
fun isBuyAllowed(): Boolean
|
||||||
|
|
@ -12,10 +18,17 @@ interface Exchanger {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExchangeService : Exchanger, ExchangeUrlBuilder {
|
interface ExchangeService : Exchanger, ExchangeUrlBuilder {
|
||||||
|
|
||||||
|
val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||||
|
|
||||||
suspend fun update()
|
suspend fun update()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun dummy(): ExchangeService = object : ExchangeService {
|
fun dummy(): ExchangeService = object : ExchangeService {
|
||||||
|
|
||||||
|
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus> =
|
||||||
|
MutableStateFlow(value = lceLoading())
|
||||||
|
|
||||||
override suspend fun update() {}
|
override suspend fun update() {}
|
||||||
override fun isBuyAllowed(): Boolean = false
|
override fun isBuyAllowed(): Boolean = false
|
||||||
override fun isSellAllowed(): Boolean = false
|
override fun isSellAllowed(): Boolean = false
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,8 @@ internal val Blockchain.mercuryoNetwork: String?
|
||||||
Blockchain.CasperTestnet -> null
|
Blockchain.CasperTestnet -> null
|
||||||
Blockchain.Core -> null
|
Blockchain.Core -> null
|
||||||
Blockchain.CoreTestnet -> null
|
Blockchain.CoreTestnet -> null
|
||||||
|
Blockchain.Chiliz -> null
|
||||||
|
Blockchain.ChilizTestnet -> null
|
||||||
Blockchain.Unknown -> null
|
Blockchain.Unknown -> null
|
||||||
Blockchain.Xodex -> null
|
Blockchain.Xodex -> null
|
||||||
Blockchain.Canxium -> null
|
Blockchain.Canxium -> null
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,19 @@ import com.tangem.common.extensions.calculateSha512
|
||||||
import com.tangem.common.extensions.toHexString
|
import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.common.services.Result
|
import com.tangem.common.services.Result
|
||||||
import com.tangem.common.services.performRequest
|
import com.tangem.common.services.performRequest
|
||||||
|
import com.tangem.domain.core.utils.lceContent
|
||||||
|
import com.tangem.domain.core.utils.lceError
|
||||||
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.tap.domain.model.Currency
|
import com.tangem.tap.domain.model.Currency
|
||||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||||
|
import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus
|
||||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,10 +26,34 @@ import java.util.concurrent.CopyOnWriteArrayList
|
||||||
*/
|
*/
|
||||||
internal class MercuryoService(private val environment: MercuryoEnvironment) : ExchangeService {
|
internal class MercuryoService(private val environment: MercuryoEnvironment) : ExchangeService {
|
||||||
|
|
||||||
|
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||||
|
get() = _initializationStatus
|
||||||
|
|
||||||
|
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
|
||||||
|
MutableStateFlow(value = lceLoading())
|
||||||
|
|
||||||
private val api: MercuryoApi = environment.mercuryoApi
|
private val api: MercuryoApi = environment.mercuryoApi
|
||||||
|
|
||||||
private val availableMercuryoCurrencies = CopyOnWriteArrayList<MercuryoCurrenciesResponse.MercuryoCryptoCurrency>()
|
private val availableMercuryoCurrencies = CopyOnWriteArrayList<MercuryoCurrenciesResponse.MercuryoCryptoCurrency>()
|
||||||
|
|
||||||
|
override suspend fun update() {
|
||||||
|
Timber.i("Start updating")
|
||||||
|
_initializationStatus.value = lceLoading()
|
||||||
|
|
||||||
|
val result = performRequest { api.currencies(environment.apiVersion) }
|
||||||
|
when {
|
||||||
|
result is Result.Success && result.data.status == RESPONSE_SUCCESS_STATUS_CODE -> {
|
||||||
|
handleSuccessfullyUpdatedData(data = result.data.data)
|
||||||
|
}
|
||||||
|
result is Result.Failure -> {
|
||||||
|
availableMercuryoCurrencies.clear()
|
||||||
|
|
||||||
|
Timber.e("Failed to load currencies", result.error)
|
||||||
|
_initializationStatus.value = result.error.lceError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun isBuyAllowed(): Boolean = true
|
override fun isBuyAllowed(): Boolean = true
|
||||||
|
|
||||||
override fun isSellAllowed(): Boolean = false
|
override fun isSellAllowed(): Boolean = false
|
||||||
|
|
@ -42,18 +73,6 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
|
||||||
|
|
||||||
override fun availableForSell(currency: Currency): Boolean = false
|
override fun availableForSell(currency: Currency): Boolean = false
|
||||||
|
|
||||||
override suspend fun update() {
|
|
||||||
val result = performRequest { api.currencies(environment.apiVersion) }
|
|
||||||
when {
|
|
||||||
result is Result.Success && result.data.status == RESPONSE_SUCCESS_STATUS_CODE -> {
|
|
||||||
handleSuccessfullyUpdatedData(data = result.data.data)
|
|
||||||
}
|
|
||||||
result is Result.Failure -> {
|
|
||||||
availableMercuryoCurrencies.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUrl(
|
override fun getUrl(
|
||||||
action: CurrencyExchangeManager.Action,
|
action: CurrencyExchangeManager.Action,
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
|
|
@ -89,6 +108,9 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
|
||||||
private fun handleSuccessfullyUpdatedData(data: MercuryoCurrenciesResponse.Data) {
|
private fun handleSuccessfullyUpdatedData(data: MercuryoCurrenciesResponse.Data) {
|
||||||
availableMercuryoCurrencies.clear()
|
availableMercuryoCurrencies.clear()
|
||||||
availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies)
|
availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies)
|
||||||
|
|
||||||
|
Timber.i("Successfully updated")
|
||||||
|
_initializationStatus.value = lceContent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase()
|
private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase()
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,20 @@ import com.tangem.common.services.Result
|
||||||
import com.tangem.common.services.performRequest
|
import com.tangem.common.services.performRequest
|
||||||
import com.tangem.datasource.api.common.createRetrofitInstance
|
import com.tangem.datasource.api.common.createRetrofitInstance
|
||||||
import com.tangem.domain.common.extensions.withIOContext
|
import com.tangem.domain.common.extensions.withIOContext
|
||||||
|
import com.tangem.domain.core.utils.lceContent
|
||||||
|
import com.tangem.domain.core.utils.lceError
|
||||||
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
import com.tangem.tap.domain.model.Currency
|
import com.tangem.tap.domain.model.Currency
|
||||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||||
|
import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus
|
||||||
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
|
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME
|
||||||
import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPayAvailableCurrency
|
import com.tangem.tap.network.exchangeServices.moonpay.models.MoonPayAvailableCurrency
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import timber.log.Timber
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
|
@ -23,6 +30,12 @@ class MoonPayService(
|
||||||
private val logEnabled: Boolean,
|
private val logEnabled: Boolean,
|
||||||
) : ExchangeService {
|
) : ExchangeService {
|
||||||
|
|
||||||
|
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||||
|
get() = _initializationStatus
|
||||||
|
|
||||||
|
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
|
||||||
|
MutableStateFlow(value = lceLoading())
|
||||||
|
|
||||||
private val api: MoonPayApi by lazy {
|
private val api: MoonPayApi by lazy {
|
||||||
createRetrofitInstance(
|
createRetrofitInstance(
|
||||||
baseUrl = MoonPayApi.MOOONPAY_BASE_URL,
|
baseUrl = MoonPayApi.MOOONPAY_BASE_URL,
|
||||||
|
|
@ -34,14 +47,25 @@ class MoonPayService(
|
||||||
|
|
||||||
override suspend fun update() {
|
override suspend fun update() {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
|
Timber.i("Start updating")
|
||||||
|
_initializationStatus.value = lceLoading()
|
||||||
|
|
||||||
performRequest {
|
performRequest {
|
||||||
val userStatus = when (val result = performRequest { api.getUserStatus(apiKey) }) {
|
val userStatus = when (val result = performRequest { api.getUserStatus(apiKey) }) {
|
||||||
is Result.Failure -> return@performRequest
|
is Result.Failure -> {
|
||||||
|
Timber.e("Failed to load user status", result.error)
|
||||||
|
_initializationStatus.value = result.error.lceError()
|
||||||
|
return@performRequest
|
||||||
|
}
|
||||||
is Result.Success -> result.data
|
is Result.Success -> result.data
|
||||||
}
|
}
|
||||||
|
|
||||||
val currencies = when (val result = performRequest { api.getCurrencies(apiKey) }) {
|
val currencies = when (val result = performRequest { api.getCurrencies(apiKey) }) {
|
||||||
is Result.Failure -> return@performRequest
|
is Result.Failure -> {
|
||||||
|
Timber.e("Failed to load currencies", result.error)
|
||||||
|
_initializationStatus.value = result.error.lceError()
|
||||||
|
return@performRequest
|
||||||
|
}
|
||||||
is Result.Success -> result.data
|
is Result.Success -> result.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +82,8 @@ class MoonPayService(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timber.i("Successfully updated")
|
||||||
|
_initializationStatus.value = lceContent()
|
||||||
status = MoonPayStatus(currenciesToSell, userStatus, currencies)
|
status = MoonPayStatus(currenciesToSell, userStatus, currencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
||||||
Filecoin -> MoonPaySupportedCurrency(networkCode = "filecoin", currencyCode = "fil")
|
Filecoin -> MoonPaySupportedCurrency(networkCode = "filecoin", currencyCode = "fil")
|
||||||
Sui -> MoonPaySupportedCurrency(networkCode = "sui", currencyCode = "sui")
|
Sui -> MoonPaySupportedCurrency(networkCode = "sui", currencyCode = "sui")
|
||||||
Core -> MoonPaySupportedCurrency(networkCode = "core", currencyCode = "core")
|
Core -> MoonPaySupportedCurrency(networkCode = "core", currencyCode = "core")
|
||||||
|
Chiliz -> MoonPaySupportedCurrency(networkCode = "ethereum", currencyCode = "chz")
|
||||||
ArbitrumTestnet -> null
|
ArbitrumTestnet -> null
|
||||||
AvalancheTestnet -> null
|
AvalancheTestnet -> null
|
||||||
BinanceTestnet -> null
|
BinanceTestnet -> null
|
||||||
|
|
@ -139,6 +140,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
||||||
Casper -> null
|
Casper -> null
|
||||||
CasperTestnet -> null
|
CasperTestnet -> null
|
||||||
CoreTestnet -> null
|
CoreTestnet -> null
|
||||||
|
ChilizTestnet -> null
|
||||||
Unknown -> null
|
Unknown -> null
|
||||||
Xodex -> null
|
Xodex -> null
|
||||||
Canxium -> null
|
Canxium -> null
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.tangem.tap.proxy
|
package com.tangem.tap.proxy
|
||||||
|
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.redux.ReduxStateHolder
|
import com.tangem.domain.redux.ReduxStateHolder
|
||||||
import com.tangem.domain.redux.StateDialog
|
import com.tangem.domain.redux.StateDialog
|
||||||
|
|
@ -26,10 +25,8 @@ class AppStateHolder @Inject constructor() : ReduxStateHolder {
|
||||||
var mainStore: Store<AppState>? = null
|
var mainStore: Store<AppState>? = null
|
||||||
var tangemSdkManager: TangemSdkManager? = null
|
var tangemSdkManager: TangemSdkManager? = null
|
||||||
var exchangeService: ExchangeService? = null
|
var exchangeService: ExchangeService? = null
|
||||||
|
var buyService: ExchangeService? = null
|
||||||
fun getActualCard(): CardDTO? {
|
var sellService: ExchangeService? = null
|
||||||
return scanResponse?.card
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispatch(action: Action) {
|
override fun dispatch(action: Action) {
|
||||||
mainStore?.dispatch(action)
|
mainStore?.dispatch(action)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
||||||
|
|
||||||
override val stack: Value<ChildStack<AppRoute, Child>> = childStack(
|
override val stack: Value<ChildStack<AppRoute, Child>> = childStack(
|
||||||
source = navigationProvider.getOrCreateTyped(),
|
source = navigationProvider.getOrCreateTyped(),
|
||||||
serializer = AppRoute.serializer(),
|
serializer = AppRoute.serializer(), // TODO Maybe set this to null for AppRoute.Onboarding case. Need to check
|
||||||
initialConfiguration = getInitialRoute(),
|
initialConfiguration = getInitialRoute(),
|
||||||
handleBackButton = false,
|
handleBackButton = false,
|
||||||
childFactory = ::child,
|
childFactory = ::child,
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,8 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||||
import com.tangem.features.onramp.component.BuyCryptoComponent
|
|
||||||
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
|
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
|
||||||
import com.tangem.features.onramp.component.OnrampComponent
|
import com.tangem.features.onramp.component.*
|
||||||
import com.tangem.features.onramp.component.SellCryptoComponent
|
|
||||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||||
import com.tangem.features.send.api.navigation.SendRouter
|
import com.tangem.features.send.api.navigation.SendRouter
|
||||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||||
|
|
@ -51,8 +49,10 @@ internal class ChildFactory @Inject constructor(
|
||||||
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
|
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
|
||||||
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
|
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
|
||||||
private val onrampComponentFactory: OnrampComponent.Factory,
|
private val onrampComponentFactory: OnrampComponent.Factory,
|
||||||
|
private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory,
|
||||||
private val buyCryptoComponentFactory: BuyCryptoComponent.Factory,
|
private val buyCryptoComponentFactory: BuyCryptoComponent.Factory,
|
||||||
private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
|
private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
|
||||||
|
private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory,
|
||||||
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
|
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
|
||||||
private val sendRouter: SendRouter,
|
private val sendRouter: SendRouter,
|
||||||
private val tokenDetailsRouter: TokenDetailsRouter,
|
private val tokenDetailsRouter: TokenDetailsRouter,
|
||||||
|
|
@ -197,10 +197,17 @@ internal class ChildFactory @Inject constructor(
|
||||||
is AppRoute.Onramp -> {
|
is AppRoute.Onramp -> {
|
||||||
route.asComponentChild(
|
route.asComponentChild(
|
||||||
contextProvider = contextProvider(route, contextFactory),
|
contextProvider = contextProvider(route, contextFactory),
|
||||||
params = OnrampComponent.Params(),
|
params = OnrampComponent.Params(route.currency),
|
||||||
componentFactory = onrampComponentFactory,
|
componentFactory = onrampComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is AppRoute.OnrampSuccess -> {
|
||||||
|
route.asComponentChild(
|
||||||
|
contextProvider = contextProvider(route, contextFactory),
|
||||||
|
params = OnrampSuccessComponent.Params(route.txId),
|
||||||
|
componentFactory = onrampSuccessComponentFactory,
|
||||||
|
)
|
||||||
|
}
|
||||||
is AppRoute.BuyCrypto -> {
|
is AppRoute.BuyCrypto -> {
|
||||||
route.asComponentChild(
|
route.asComponentChild(
|
||||||
contextProvider = contextProvider(route, contextFactory),
|
contextProvider = contextProvider(route, contextFactory),
|
||||||
|
|
@ -215,10 +222,20 @@ internal class ChildFactory @Inject constructor(
|
||||||
componentFactory = sellCryptoComponentFactory,
|
componentFactory = sellCryptoComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is AppRoute.SwapCrypto -> {
|
||||||
|
route.asComponentChild(
|
||||||
|
contextProvider = contextProvider(route, contextFactory),
|
||||||
|
params = SwapSelectTokensComponent.Params(userWalletId = route.userWalletId),
|
||||||
|
componentFactory = swapSelectTokensComponentFactory,
|
||||||
|
)
|
||||||
|
}
|
||||||
is AppRoute.Onboarding -> {
|
is AppRoute.Onboarding -> {
|
||||||
route.asComponentChild(
|
route.asComponentChild(
|
||||||
contextProvider = contextProvider(route, contextFactory),
|
contextProvider = contextProvider(route, contextFactory),
|
||||||
params = OnboardingEntryComponent.Params(route.scanResponse),
|
params = OnboardingEntryComponent.Params(
|
||||||
|
scanResponse = route.scanResponse,
|
||||||
|
startBackupFlow = route.startFromBackup,
|
||||||
|
),
|
||||||
componentFactory = onboardingEntryComponentFactory,
|
componentFactory = onboardingEntryComponentFactory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -219,16 +219,24 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Swap(
|
data class Swap(
|
||||||
val currency: CryptoCurrency,
|
val currencyFrom: CryptoCurrency,
|
||||||
|
val currencyTo: CryptoCurrency? = null,
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
val isInitialReverseOrder: Boolean = false,
|
val isInitialReverseOrder: Boolean = false,
|
||||||
) : AppRoute(path = "/swap/${currency.id.value}/${userWalletId.stringValue}/$isInitialReverseOrder"),
|
) : AppRoute(
|
||||||
|
path = "/swap" +
|
||||||
|
"/${currencyFrom.id.value}" +
|
||||||
|
"/${currencyTo?.id?.value}" +
|
||||||
|
"/${userWalletId.stringValue}" +
|
||||||
|
"/$isInitialReverseOrder",
|
||||||
|
),
|
||||||
RouteBundleParams {
|
RouteBundleParams {
|
||||||
|
|
||||||
override fun getBundle(): Bundle = bundle(serializer())
|
override fun getBundle(): Bundle = bundle(serializer())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CURRENCY_BUNDLE_KEY = "currency"
|
const val CURRENCY_FROM_KEY = "currencyFrom"
|
||||||
|
const val CURRENCY_TO_KEY = "currencyTo"
|
||||||
const val USER_WALLET_ID_KEY = "userWalletId"
|
const val USER_WALLET_ID_KEY = "userWalletId"
|
||||||
const val IS_INITIAL_REVERSE_ORDER = "isInitialReverseOrder"
|
const val IS_INITIAL_REVERSE_ORDER = "isInitialReverseOrder"
|
||||||
}
|
}
|
||||||
|
|
@ -287,7 +295,16 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Onramp : AppRoute(path = "/onramp")
|
data class Onramp(val currency: CryptoCurrency) : AppRoute(path = "/onramp/${currency.symbol}"), RouteBundleParams {
|
||||||
|
override fun getBundle(): Bundle = bundle(serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class OnrampSuccess(
|
||||||
|
val txId: String,
|
||||||
|
) : AppRoute(path = "/onramp/success/$txId"), RouteBundleParams {
|
||||||
|
override fun getBundle(): Bundle = bundle(serializer())
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BuyCrypto(
|
data class BuyCrypto(
|
||||||
|
|
@ -299,6 +316,14 @@ sealed class AppRoute(val path: String) : Route {
|
||||||
val userWalletId: UserWalletId,
|
val userWalletId: UserWalletId,
|
||||||
) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}")
|
) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SwapCrypto(
|
||||||
|
val userWalletId: UserWalletId,
|
||||||
|
) : AppRoute(path = "/swap_crypto/${userWalletId.stringValue}")
|
||||||
|
|
||||||
// Onboarding V2
|
// Onboarding V2
|
||||||
data class Onboarding(val scanResponse: ScanResponse) : AppRoute(path = "/onboarding_v2")
|
data class Onboarding(
|
||||||
|
val scanResponse: ScanResponse,
|
||||||
|
val startFromBackup: Boolean,
|
||||||
|
) : AppRoute(path = "/onboarding_v2${if (startFromBackup) "/backup" else ""}")
|
||||||
}
|
}
|
||||||
|
|
@ -31,10 +31,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||||
import com.tangem.core.ui.components.containers.FooterContainer
|
import com.tangem.core.ui.components.containers.FooterContainer
|
||||||
import com.tangem.core.ui.components.inputrow.InputRowDefault
|
import com.tangem.core.ui.components.inputrow.InputRowDefault
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
|
||||||
import com.tangem.core.ui.extensions.wrappedList
|
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -118,7 +115,7 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh
|
||||||
@Composable
|
@Composable
|
||||||
private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) {
|
private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) {
|
||||||
FooterContainer(
|
FooterContainer(
|
||||||
footer = stringResource(id = R.string.give_permission_policy_type_footer),
|
footer = resourceReference(R.string.give_permission_policy_type_footer),
|
||||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||||
) {
|
) {
|
||||||
AmountItem(
|
AmountItem(
|
||||||
|
|
@ -130,7 +127,7 @@ private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest)
|
||||||
}
|
}
|
||||||
SpacerH16()
|
SpacerH16()
|
||||||
FooterContainer(
|
FooterContainer(
|
||||||
footer = data.footerText.resolveReference(),
|
footer = data.footerText,
|
||||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||||
) {
|
) {
|
||||||
FeeItem(fee = data.fee)
|
FeeItem(fee = data.fee)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
package com.tangem.common.ui.expressStatus
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.common.ui.expressStatus.state.ExpressLinkUM
|
||||||
|
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState
|
||||||
|
import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM
|
||||||
|
import com.tangem.common.ui.expressStatus.state.ExpressStatusUM
|
||||||
|
import com.tangem.core.ui.components.SpacerWMax
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block with express statuses
|
||||||
|
*
|
||||||
|
* @param state ui holder
|
||||||
|
* @param modifier modifier
|
||||||
|
* @see [Figma](https://www.figma.com/design/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?node-id=18459-26521&t=4jox7bfqUiXnm2h1-4)
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ExpressStatusBlock(state: ExpressStatusUM, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||||
|
.background(TangemTheme.colors.background.action)
|
||||||
|
.padding(
|
||||||
|
vertical = TangemTheme.dimens.spacing14,
|
||||||
|
horizontal = TangemTheme.dimens.spacing12,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(bottom = TangemTheme.dimens.spacing16),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = state.title.resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
SpacerWMax()
|
||||||
|
AnimatedVisibility(visible = state.link is ExpressLinkUM.Content) {
|
||||||
|
val link = remember(this) { state.link as ExpressLinkUM.Content }
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.clickable { link.onClick() },
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = link.icon),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = TangemTheme.colors.icon.informative,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(TangemTheme.dimens.spacing16)
|
||||||
|
.padding(end = TangemTheme.dimens.spacing2),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = link.text.resolveReference(),
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
state.statuses.forEachIndexed { index, item ->
|
||||||
|
ExpressStatusStep(item, index == state.statuses.lastIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExpressStatusStep(status: ExpressStatusItemUM, isLast: Boolean) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = status,
|
||||||
|
label = "Exchange Step Change Success",
|
||||||
|
transitionSpec = {
|
||||||
|
fadeIn(tween(durationMillis = 220)) togetherWith
|
||||||
|
fadeOut(tween(durationMillis = 220))
|
||||||
|
},
|
||||||
|
) { content ->
|
||||||
|
Row {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
when (content.state) {
|
||||||
|
ExpressStatusItemState.Active -> StepInProgress()
|
||||||
|
ExpressStatusItemState.Default -> StepDefault()
|
||||||
|
ExpressStatusItemState.Done -> Step(
|
||||||
|
iconRes = R.drawable.ic_check_24,
|
||||||
|
iconColor = TangemTheme.colors.icon.primary1,
|
||||||
|
borderColor = TangemTheme.colors.field.focused,
|
||||||
|
)
|
||||||
|
ExpressStatusItemState.Error -> Step(
|
||||||
|
iconRes = R.drawable.ic_close_24,
|
||||||
|
iconColor = TangemTheme.colors.icon.warning,
|
||||||
|
)
|
||||||
|
ExpressStatusItemState.Warning -> Step(
|
||||||
|
iconRes = R.drawable.ic_close_24,
|
||||||
|
iconColor = TangemTheme.colors.icon.attention,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!isLast) {
|
||||||
|
StepSeparator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val textColor = when (status.state) {
|
||||||
|
ExpressStatusItemState.Active -> TangemTheme.colors.text.primary1
|
||||||
|
ExpressStatusItemState.Default -> TangemTheme.colors.text.disabled
|
||||||
|
ExpressStatusItemState.Done -> TangemTheme.colors.text.primary1
|
||||||
|
ExpressStatusItemState.Error -> TangemTheme.colors.text.warning
|
||||||
|
ExpressStatusItemState.Warning -> TangemTheme.colors.text.attention
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = content.text.resolveReference(),
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
color = textColor,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = TangemTheme.dimens.spacing12),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StepDefault() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(TangemTheme.dimens.size20)
|
||||||
|
.border(
|
||||||
|
width = TangemTheme.dimens.size1_5,
|
||||||
|
color = TangemTheme.colors.field.focused,
|
||||||
|
shape = CircleShape,
|
||||||
|
)
|
||||||
|
.padding(TangemTheme.dimens.spacing2),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Step(iconColor: Color, @DrawableRes iconRes: Int, borderColor: Color = iconColor) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = iconRes),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = iconColor,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(TangemTheme.dimens.size20)
|
||||||
|
.border(
|
||||||
|
width = TangemTheme.dimens.size1_5,
|
||||||
|
color = borderColor,
|
||||||
|
shape = CircleShape,
|
||||||
|
)
|
||||||
|
.padding(TangemTheme.dimens.spacing2),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StepInProgress() {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
color = TangemTheme.colors.icon.primary1,
|
||||||
|
strokeWidth = TangemTheme.dimens.size2,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(TangemTheme.dimens.spacing2)
|
||||||
|
.size(TangemTheme.dimens.size14),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StepSeparator() {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = TangemTheme.dimens.spacing2)
|
||||||
|
.size(
|
||||||
|
width = TangemTheme.dimens.size1_5,
|
||||||
|
height = TangemTheme.dimens.size10,
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
color = TangemTheme.colors.field.focused,
|
||||||
|
shape = CircleShape,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun Preview_ExchangeStatusBlock() {
|
||||||
|
val state = ExpressStatusUM(
|
||||||
|
title = resourceReference(R.string.express_exchange_status_title),
|
||||||
|
link = ExpressLinkUM.Content(
|
||||||
|
icon = R.drawable.ic_alert_24,
|
||||||
|
text = resourceReference(R.string.common_go_to_provider),
|
||||||
|
onClick = {},
|
||||||
|
),
|
||||||
|
statuses = persistentListOf(
|
||||||
|
ExpressStatusItemUM(text = stringReference("Done"), state = ExpressStatusItemState.Done),
|
||||||
|
ExpressStatusItemUM(text = stringReference("Active"), state = ExpressStatusItemState.Active),
|
||||||
|
ExpressStatusItemUM(text = stringReference("Warning"), state = ExpressStatusItemState.Warning),
|
||||||
|
ExpressStatusItemUM(text = stringReference("Error"), state = ExpressStatusItemState.Error),
|
||||||
|
ExpressStatusItemUM(text = stringReference("Default"), state = ExpressStatusItemState.Default),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
TangemThemePreview {
|
||||||
|
ExpressStatusBlock(state = state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.tangem.common.ui.expressStatus
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.common.ui.notifications.ExpressNotificationsUM
|
||||||
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
|
import com.tangem.core.ui.components.notifications.Notification
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExpressStatusNotificationBlock(state: NotificationUM?) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = state,
|
||||||
|
modifier = Modifier.padding(top = 12.dp),
|
||||||
|
label = "Express Status Notification Change",
|
||||||
|
) { notification ->
|
||||||
|
if (notification?.config != null) {
|
||||||
|
Notification(
|
||||||
|
config = notification.config,
|
||||||
|
iconTint = when (state) {
|
||||||
|
is ExpressNotificationsUM.NeedVerification -> TangemTheme.colors.icon.attention
|
||||||
|
is ExpressNotificationsUM.FailedByProvider -> TangemTheme.colors.icon.warning
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
containerColor = TangemTheme.colors.background.action,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.tangem.common.ui.expressStatus.state
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UI data holder for express status block
|
||||||
|
*
|
||||||
|
* @property title block title
|
||||||
|
* @property link provider web link
|
||||||
|
* @property statuses list of possible and active statuses
|
||||||
|
*/
|
||||||
|
data class ExpressStatusUM(
|
||||||
|
val title: TextReference,
|
||||||
|
val link: ExpressLinkUM,
|
||||||
|
val statuses: ImmutableList<ExpressStatusItemUM>,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider web link for express status block.
|
||||||
|
* [Empty] if no link needed
|
||||||
|
* [Content] if link is provided and displayed
|
||||||
|
*/
|
||||||
|
@Stable
|
||||||
|
sealed class ExpressLinkUM {
|
||||||
|
data object Empty : ExpressLinkUM()
|
||||||
|
data class Content(
|
||||||
|
@DrawableRes val icon: Int,
|
||||||
|
val text: TextReference,
|
||||||
|
val onClick: () -> Unit,
|
||||||
|
) : ExpressLinkUM()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single status item in express status block
|
||||||
|
*/
|
||||||
|
data class ExpressStatusItemUM(
|
||||||
|
val text: TextReference,
|
||||||
|
val state: ExpressStatusItemState,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available status states for express status block
|
||||||
|
*/
|
||||||
|
enum class ExpressStatusItemState {
|
||||||
|
Active,
|
||||||
|
Default,
|
||||||
|
Done,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.tangem.common.ui.notifications
|
||||||
|
|
||||||
|
import com.tangem.common.ui.R
|
||||||
|
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
|
||||||
|
object ExpressNotificationsUM {
|
||||||
|
|
||||||
|
data class NeedVerification(val onGoToProviderClick: () -> Unit) : NotificationUM.Warning(
|
||||||
|
title = resourceReference(R.string.express_exchange_notification_verification_title),
|
||||||
|
subtitle = resourceReference(R.string.express_exchange_notification_verification_text),
|
||||||
|
iconResId = R.drawable.ic_alert_triangle_20,
|
||||||
|
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||||
|
text = resourceReference(R.string.common_go_to_provider),
|
||||||
|
onClick = onGoToProviderClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FailedByProvider(val onGoToProviderClick: () -> Unit) : NotificationUM.Error(
|
||||||
|
title = resourceReference(R.string.express_exchange_notification_failed_title),
|
||||||
|
subtitle = resourceReference(R.string.express_exchange_notification_failed_text),
|
||||||
|
iconResId = R.drawable.ic_alert_circle_24,
|
||||||
|
buttonState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||||
|
text = resourceReference(R.string.common_go_to_provider),
|
||||||
|
onClick = onGoToProviderClick,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.core.ui.format.bigdecimal.shorted
|
import com.tangem.core.ui.format.bigdecimal.shorted
|
||||||
|
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
sealed class NotificationUM(val config: NotificationConfig) {
|
sealed class NotificationUM(val config: NotificationConfig) {
|
||||||
|
|
@ -307,4 +308,17 @@ sealed class NotificationUM(val config: NotificationConfig) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed interface Solana {
|
||||||
|
|
||||||
|
data class RentInfo(
|
||||||
|
private val rentInfo: CryptoCurrencyWarning.Rent,
|
||||||
|
) : Info(
|
||||||
|
title = TextReference.Res(R.string.warning_rent_fee_title),
|
||||||
|
subtitle = TextReference.Res(
|
||||||
|
id = R.string.warning_solana_rent_fee_message,
|
||||||
|
formatArgs = wrappedList(rentInfo.rent, rentInfo.exemptionAmount),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIco
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
import com.tangem.core.ui.format.bigdecimal.percent
|
import com.tangem.core.ui.format.bigdecimal.percent
|
||||||
|
|
@ -39,8 +40,8 @@ class TokenItemStateConverter(
|
||||||
private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = {
|
private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = {
|
||||||
createFiatAmountState(it, appCurrency)
|
createFiatAmountState(it, appCurrency)
|
||||||
},
|
},
|
||||||
private val onItemClick: (CryptoCurrencyStatus) -> Unit,
|
private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||||
private val onItemLongClick: ((CryptoCurrencyStatus) -> Unit)? = null,
|
private val onItemLongClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||||
|
|
||||||
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
||||||
|
|
@ -75,9 +76,11 @@ class TokenItemStateConverter(
|
||||||
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||||
fiatAmountState = requireNotNull(fiatAmountStateProvider(this)),
|
fiatAmountState = requireNotNull(fiatAmountStateProvider(this)),
|
||||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = getFormattedAmount()),
|
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = getFormattedAmount()),
|
||||||
onItemClick = { onItemClick(this) },
|
onItemClick = onItemClick?.let { onItemClick ->
|
||||||
onItemLongClick = onItemLongClick?.let {
|
{ onItemClick(it, this) }
|
||||||
{ it(this) }
|
},
|
||||||
|
onItemLongClick = onItemLongClick?.let { onItemLongClick ->
|
||||||
|
{ onItemLongClick(it, this) }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -94,9 +97,11 @@ class TokenItemStateConverter(
|
||||||
iconState = iconStateProvider(this),
|
iconState = iconStateProvider(this),
|
||||||
titleState = titleStateProvider(this),
|
titleState = titleStateProvider(this),
|
||||||
subtitleState = subtitleStateProvider(this),
|
subtitleState = subtitleStateProvider(this),
|
||||||
onItemClick = { onItemClick(this) },
|
onItemClick = onItemClick?.let { onItemClick ->
|
||||||
onItemLongClick = onItemLongClick?.let {
|
{ onItemClick(it, this) }
|
||||||
{ it(this) }
|
},
|
||||||
|
onItemLongClick = onItemLongClick?.let { onItemLongClick ->
|
||||||
|
{ onItemLongClick(it, this) }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -107,8 +112,8 @@ class TokenItemStateConverter(
|
||||||
iconState = iconStateProvider(this),
|
iconState = iconStateProvider(this),
|
||||||
titleState = titleStateProvider(this),
|
titleState = titleStateProvider(this),
|
||||||
subtitleState = subtitleStateProvider(this),
|
subtitleState = subtitleStateProvider(this),
|
||||||
onItemLongClick = onItemLongClick?.let {
|
onItemLongClick = onItemLongClick?.let { onItemLongClick ->
|
||||||
{ it(this) }
|
{ onItemLongClick(it, this) }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +127,7 @@ class TokenItemStateConverter(
|
||||||
is CryptoCurrencyStatus.Unreachable,
|
is CryptoCurrencyStatus.Unreachable,
|
||||||
is CryptoCurrencyStatus.NoAmount,
|
is CryptoCurrencyStatus.NoAmount,
|
||||||
-> {
|
-> {
|
||||||
TokenItemState.TitleState.Content(text = currencyStatus.currency.name)
|
TokenItemState.TitleState.Content(text = stringReference(currencyStatus.currency.name))
|
||||||
}
|
}
|
||||||
is CryptoCurrencyStatus.Loaded,
|
is CryptoCurrencyStatus.Loaded,
|
||||||
is CryptoCurrencyStatus.Custom,
|
is CryptoCurrencyStatus.Custom,
|
||||||
|
|
@ -130,7 +135,7 @@ class TokenItemStateConverter(
|
||||||
is CryptoCurrencyStatus.NoAccount,
|
is CryptoCurrencyStatus.NoAccount,
|
||||||
-> {
|
-> {
|
||||||
TokenItemState.TitleState.Content(
|
TokenItemState.TitleState.Content(
|
||||||
text = currencyStatus.currency.name,
|
text = stringReference(currencyStatus.currency.name),
|
||||||
hasPending = value.hasCurrentNetworkTransactions,
|
hasPending = value.hasCurrentNetworkTransactions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ dependencies {
|
||||||
implementation(projects.core.utils)
|
implementation(projects.core.utils)
|
||||||
implementation(projects.libs.auth)
|
implementation(projects.libs.auth)
|
||||||
implementation(projects.domain.appTheme.models)
|
implementation(projects.domain.appTheme.models)
|
||||||
|
implementation(projects.domain.core)
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.balanceHiding.models)
|
implementation(projects.domain.balanceHiding.models)
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,12 @@ internal class DateTimeAdapter : JsonAdapter<DateTime>() {
|
||||||
|
|
||||||
@FromJson
|
@FromJson
|
||||||
override fun fromJson(reader: JsonReader): DateTime? {
|
override fun fromJson(reader: JsonReader): DateTime? {
|
||||||
val dateString = reader.nextString() ?: return null
|
return if (reader.peek() == JsonReader.Token.NULL) {
|
||||||
|
reader.nextNull<DateTime>()
|
||||||
return DateTime.parse(dateString)
|
} else {
|
||||||
|
val dateString = reader.nextString()
|
||||||
|
DateTime.parse(dateString)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToJson
|
@ToJson
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,12 @@ internal class LocalDateAdapter : JsonAdapter<LocalDate>() {
|
||||||
|
|
||||||
@FromJson
|
@FromJson
|
||||||
override fun fromJson(reader: JsonReader): LocalDate? {
|
override fun fromJson(reader: JsonReader): LocalDate? {
|
||||||
val dateString = reader.nextString()
|
return if (reader.peek() == JsonReader.Token.NULL) {
|
||||||
return LocalDate.parse(dateString, formatter)
|
reader.nextNull<LocalDate>()
|
||||||
|
} else {
|
||||||
|
val dateString = reader.nextString()
|
||||||
|
return LocalDate.parse(dateString, formatter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ToJson
|
@ToJson
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.datasource.api.markets.models.response
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.joda.time.DateTime
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
|
@ -26,6 +27,8 @@ data class TokenMarketInfoResponse(
|
||||||
val insights: Insights?,
|
val insights: Insights?,
|
||||||
@Json(name = "metrics")
|
@Json(name = "metrics")
|
||||||
val metrics: Metrics?,
|
val metrics: Metrics?,
|
||||||
|
@Json(name = "security_data")
|
||||||
|
val securityData: SecurityData?,
|
||||||
@Json(name = "links")
|
@Json(name = "links")
|
||||||
val links: Links?,
|
val links: Links?,
|
||||||
@Json(name = "price_performance")
|
@Json(name = "price_performance")
|
||||||
|
|
@ -144,6 +147,28 @@ data class TokenMarketInfoResponse(
|
||||||
val allTime: Range?,
|
val allTime: Range?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class SecurityData(
|
||||||
|
@Json(name = "total_security_score")
|
||||||
|
val totalSecurityScore: Float,
|
||||||
|
@Json(name = "provider_data")
|
||||||
|
val providerData: List<ProviderData>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class ProviderData(
|
||||||
|
@Json(name = "provider_id")
|
||||||
|
val providerId: String,
|
||||||
|
@Json(name = "provider_name")
|
||||||
|
val providerName: String,
|
||||||
|
@Json(name = "link")
|
||||||
|
val link: String?,
|
||||||
|
@Json(name = "security_score")
|
||||||
|
val securityScore: Float,
|
||||||
|
@Json(name = "last_audit_date")
|
||||||
|
val lastAuditDate: DateTime?,
|
||||||
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Range(
|
data class Range(
|
||||||
@Json(name = "low_price")
|
@Json(name = "low_price")
|
||||||
|
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
package com.tangem.datasource.api.onramp
|
|
||||||
|
|
||||||
import com.tangem.datasource.api.common.response.ApiResponse
|
|
||||||
import com.tangem.datasource.api.onramp.models.common.OnrampDestinationDTO
|
|
||||||
import com.tangem.datasource.api.onramp.models.request.OnrampPairsRequest
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampDataResponse
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampQuoteResponse
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampPairDTO
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
|
||||||
|
|
||||||
internal class MockedOnrampApi : OnrampApi {
|
|
||||||
override suspend fun getCurrencies(): ApiResponse<List<OnrampCurrencyDTO>> = ApiResponse.Success(
|
|
||||||
COUNTRIES.map(OnrampCountryDTO::defaultCurrency),
|
|
||||||
)
|
|
||||||
|
|
||||||
override suspend fun getCountries(): ApiResponse<List<OnrampCountryDTO>> = ApiResponse.Success(COUNTRIES + RUSSIA)
|
|
||||||
|
|
||||||
override suspend fun getCountryByIp(): ApiResponse<OnrampCountryDTO> = ApiResponse.Success(RUSSIA)
|
|
||||||
|
|
||||||
override suspend fun getPaymentMethods(): ApiResponse<List<PaymentMethodDTO>> = ApiResponse.Success(
|
|
||||||
listOf(
|
|
||||||
PaymentMethodDTO(id = "google", name = "Google Play", image = ""),
|
|
||||||
PaymentMethodDTO(id = "apple", name = "Apple Pay", image = ""),
|
|
||||||
PaymentMethodDTO(id = "card", name = "Card", image = ""),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
override suspend fun getPairs(body: OnrampPairsRequest): ApiResponse<List<OnrampPairDTO>> = ApiResponse.Success(
|
|
||||||
listOf(
|
|
||||||
OnrampPairDTO(
|
|
||||||
fromCurrencyCode = "USD",
|
|
||||||
to = OnrampDestinationDTO(contractAddress = "0xcontract_address", network = "ethereum"),
|
|
||||||
providers = listOf(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
override suspend fun getQuote(
|
|
||||||
fromCurrencyCode: String,
|
|
||||||
toContractAddress: String,
|
|
||||||
toNetwork: String,
|
|
||||||
paymentMethod: String,
|
|
||||||
countryCode: String,
|
|
||||||
fromAmount: String,
|
|
||||||
toDecimals: Int,
|
|
||||||
providerId: String,
|
|
||||||
): ApiResponse<OnrampQuoteResponse> {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getData(
|
|
||||||
fromCurrencyCode: String,
|
|
||||||
toContractAddress: String,
|
|
||||||
toNetwork: String,
|
|
||||||
paymentMethod: String,
|
|
||||||
countryCode: String,
|
|
||||||
fromAmount: String,
|
|
||||||
toDecimals: Int,
|
|
||||||
providerId: String,
|
|
||||||
toAddress: String,
|
|
||||||
redirectUrl: String,
|
|
||||||
language: String?,
|
|
||||||
theme: String?,
|
|
||||||
requestId: String,
|
|
||||||
): ApiResponse<OnrampDataResponse> {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getStatus(txId: String): ApiResponse<OnrampStatusResponse> {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
private val RUSSIA = OnrampCountryDTO(
|
|
||||||
name = "Russia",
|
|
||||||
code = "RU",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/ru.svg",
|
|
||||||
alpha3 = "RUS",
|
|
||||||
continent = "",
|
|
||||||
defaultCurrency = OnrampCurrencyDTO(
|
|
||||||
name = "Russian ruble",
|
|
||||||
code = "RUB",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/ru.svg",
|
|
||||||
precision = 2,
|
|
||||||
),
|
|
||||||
onrampAvailable = false,
|
|
||||||
)
|
|
||||||
private val COUNTRIES = listOf(
|
|
||||||
OnrampCountryDTO(
|
|
||||||
name = "United States of America",
|
|
||||||
code = "USA",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/us.svg",
|
|
||||||
alpha3 = "USA",
|
|
||||||
continent = "",
|
|
||||||
defaultCurrency = OnrampCurrencyDTO(
|
|
||||||
name = "US Dollar",
|
|
||||||
code = "USD",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/us.svg",
|
|
||||||
precision = 2,
|
|
||||||
),
|
|
||||||
onrampAvailable = true,
|
|
||||||
),
|
|
||||||
OnrampCountryDTO(
|
|
||||||
name = "Europe Union",
|
|
||||||
code = "EU",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/eu.svg",
|
|
||||||
alpha3 = "EUR",
|
|
||||||
continent = "",
|
|
||||||
defaultCurrency = OnrampCurrencyDTO(
|
|
||||||
name = "Euro",
|
|
||||||
code = "EUR",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/eu.svg",
|
|
||||||
precision = 2,
|
|
||||||
),
|
|
||||||
onrampAvailable = true,
|
|
||||||
),
|
|
||||||
OnrampCountryDTO(
|
|
||||||
name = "Great Britain",
|
|
||||||
code = "GB",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/gb.svg",
|
|
||||||
alpha3 = "GB",
|
|
||||||
continent = "",
|
|
||||||
defaultCurrency = OnrampCurrencyDTO(
|
|
||||||
name = "British Pound Sterling",
|
|
||||||
code = "GBP",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/gb.svg",
|
|
||||||
precision = 2,
|
|
||||||
),
|
|
||||||
onrampAvailable = true,
|
|
||||||
),
|
|
||||||
OnrampCountryDTO(
|
|
||||||
name = "CANADA",
|
|
||||||
code = "CA",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/ca.svg",
|
|
||||||
alpha3 = "CA",
|
|
||||||
continent = "",
|
|
||||||
defaultCurrency = OnrampCurrencyDTO(
|
|
||||||
name = "Canadian Dollar",
|
|
||||||
code = "CAD",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/ca.svg",
|
|
||||||
precision = 2,
|
|
||||||
),
|
|
||||||
onrampAvailable = true,
|
|
||||||
),
|
|
||||||
OnrampCountryDTO(
|
|
||||||
name = "Hon Kong",
|
|
||||||
code = "HK",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/hk.svg",
|
|
||||||
alpha3 = "HK",
|
|
||||||
continent = "",
|
|
||||||
defaultCurrency = OnrampCurrencyDTO(
|
|
||||||
name = "Hon Kong Dollar",
|
|
||||||
code = "HKD",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/hk.svg",
|
|
||||||
precision = 2,
|
|
||||||
),
|
|
||||||
onrampAvailable = true,
|
|
||||||
),
|
|
||||||
OnrampCountryDTO(
|
|
||||||
name = "Australia",
|
|
||||||
code = "AU",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/au.svg",
|
|
||||||
alpha3 = "AU",
|
|
||||||
continent = "",
|
|
||||||
defaultCurrency = OnrampCurrencyDTO(
|
|
||||||
name = "Australian Dollar",
|
|
||||||
code = "AUD",
|
|
||||||
image = "https://hatscripts.github.io/circle-flags/flags/au.svg",
|
|
||||||
precision = 2,
|
|
||||||
),
|
|
||||||
onrampAvailable = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -14,8 +14,8 @@ data class OnrampStatusResponse(
|
||||||
@Json(name = "payoutAddress")
|
@Json(name = "payoutAddress")
|
||||||
val payoutAddress: String,
|
val payoutAddress: String,
|
||||||
|
|
||||||
// @Json(name = "status")
|
@Json(name = "status")
|
||||||
// val status: ???
|
val status: Status,
|
||||||
|
|
||||||
@Json(name = "failReason")
|
@Json(name = "failReason")
|
||||||
val failReason: String?,
|
val failReason: String?,
|
||||||
|
|
@ -48,14 +48,46 @@ data class OnrampStatusResponse(
|
||||||
val toDecimals: String,
|
val toDecimals: String,
|
||||||
|
|
||||||
@Json(name = "toAmount")
|
@Json(name = "toAmount")
|
||||||
val toAmount: String,
|
val toAmount: String?,
|
||||||
|
|
||||||
@Json(name = "toActualAmount")
|
@Json(name = "toActualAmount")
|
||||||
val toActualAmount: String,
|
val toActualAmount: String?,
|
||||||
|
|
||||||
@Json(name = "paymentMethod")
|
@Json(name = "paymentMethod")
|
||||||
val paymentMethod: String,
|
val paymentMethod: String,
|
||||||
|
|
||||||
@Json(name = "countryCode")
|
@Json(name = "countryCode")
|
||||||
val countryCode: String,
|
val countryCode: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
@Json(name = "created")
|
||||||
|
Created,
|
||||||
|
|
||||||
|
@Json(name = "expired")
|
||||||
|
Expired,
|
||||||
|
|
||||||
|
@Json(name = "waiting-for-payment")
|
||||||
|
WaitingForPayment,
|
||||||
|
|
||||||
|
@Json(name = "payment-processing")
|
||||||
|
PaymentProcessing,
|
||||||
|
|
||||||
|
@Json(name = "verifying")
|
||||||
|
Verifying,
|
||||||
|
|
||||||
|
@Json(name = "failed")
|
||||||
|
Failed,
|
||||||
|
|
||||||
|
@Json(name = "paid")
|
||||||
|
Paid,
|
||||||
|
|
||||||
|
@Json(name = "sending")
|
||||||
|
Sending,
|
||||||
|
|
||||||
|
@Json(name = "finished")
|
||||||
|
Finished,
|
||||||
|
|
||||||
|
@Json(name = "paused")
|
||||||
|
Paused,
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,6 @@ import com.tangem.datasource.api.common.config.managers.ProdApiConfigsManager
|
||||||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
import com.tangem.datasource.api.express.TangemExpressApi
|
||||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||||
import com.tangem.datasource.api.onramp.MockedOnrampApi
|
|
||||||
import com.tangem.datasource.api.onramp.OnrampApi
|
import com.tangem.datasource.api.onramp.OnrampApi
|
||||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
|
|
@ -99,24 +98,22 @@ internal object NetworkModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideOnrampApi(
|
fun provideOnrampApi(
|
||||||
// @NetworkMoshi moshi: Moshi,
|
@NetworkMoshi moshi: Moshi,
|
||||||
// @ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
// apiConfigsManager: ApiConfigsManager,
|
apiConfigsManager: ApiConfigsManager,
|
||||||
// appLogsStore: AppLogsStore,
|
appLogsStore: AppLogsStore,
|
||||||
): OnrampApi {
|
): OnrampApi {
|
||||||
// TODO: Remove when backend will be ready - [REDACTED_TASK_KEY]
|
return createApi(
|
||||||
return MockedOnrampApi()
|
id = ApiConfig.ID.Express,
|
||||||
// return createApi(
|
moshi = moshi,
|
||||||
// id = ApiConfig.ID.Express,
|
context = context,
|
||||||
// moshi = moshi,
|
apiConfigsManager = apiConfigsManager,
|
||||||
// context = context,
|
clientBuilder = {
|
||||||
// apiConfigsManager = apiConfigsManager,
|
addInterceptor(
|
||||||
// clientBuilder = {
|
NetworkLogsSaveInterceptor(appLogsStore),
|
||||||
// addInterceptor(
|
)
|
||||||
// NetworkLogsSaveInterceptor(appLogsStore),
|
},
|
||||||
// )
|
)
|
||||||
// },
|
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.datasource.di
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeDataStore
|
||||||
|
import com.tangem.datasource.local.onramp.DefaultOnrampPaymentMethodsStore
|
||||||
|
import com.tangem.datasource.local.onramp.OnrampPaymentMethodsStore
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object OnrampStoreModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideOnrampPaymentMethodsStore(): OnrampPaymentMethodsStore {
|
||||||
|
return DefaultOnrampPaymentMethodsStore(dataStore = RuntimeDataStore())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.tangem.datasource.di.exchangeservice
|
||||||
|
|
||||||
|
import com.tangem.datasource.exchangeservice.swap.DefaultSwapServiceLoader
|
||||||
|
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal interface ExchangeServiceLoaderModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindSwapServiceLoader(defaultSwapServiceLoader: DefaultSwapServiceLoader): SwapServiceLoader
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package com.tangem.datasource.exchangeservice.swap
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.common.response.getOrThrow
|
||||||
|
import com.tangem.datasource.api.express.TangemExpressApi
|
||||||
|
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
|
||||||
|
import com.tangem.datasource.api.express.models.request.AssetsRequestBody
|
||||||
|
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
||||||
|
import com.tangem.datasource.api.express.models.response.Asset
|
||||||
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
|
import com.tangem.datasource.local.token.ExpressAssetsStore
|
||||||
|
import com.tangem.domain.core.lce.Lce
|
||||||
|
import com.tangem.domain.core.utils.lceContent
|
||||||
|
import com.tangem.domain.core.utils.lceError
|
||||||
|
import com.tangem.domain.core.utils.lceLoading
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
typealias InitializationStatusFlow = MutableStateFlow<Lce<Throwable, List<Asset>>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of [SwapServiceLoader]
|
||||||
|
*
|
||||||
|
* @property tangemExpressApi express api
|
||||||
|
* @property expressAssetsStore local storage
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
internal class DefaultSwapServiceLoader @Inject constructor(
|
||||||
|
private val tangemExpressApi: TangemExpressApi,
|
||||||
|
private val expressAssetsStore: ExpressAssetsStore,
|
||||||
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
) : SwapServiceLoader {
|
||||||
|
|
||||||
|
private val initializationStatuses =
|
||||||
|
MutableStateFlow<Map<UserWalletId, InitializationStatusFlow>>(value = emptyMap())
|
||||||
|
|
||||||
|
override suspend fun update(userWalletId: UserWalletId, userTokens: UserTokensResponse) {
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
val initializationStatus = getInitializationStatusInternal(userWalletId)
|
||||||
|
|
||||||
|
initializationStatus.update { lceLoading() }
|
||||||
|
|
||||||
|
try {
|
||||||
|
val tokensList = userTokens.tokens.map {
|
||||||
|
LeastTokenInfo(
|
||||||
|
contractAddress = it.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE,
|
||||||
|
network = it.networkId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokensList.isNotEmpty()) {
|
||||||
|
val response = tangemExpressApi.getAssets(
|
||||||
|
body = AssetsRequestBody(tokensList = tokensList),
|
||||||
|
).getOrThrow()
|
||||||
|
|
||||||
|
expressAssetsStore.store(userWalletId, response)
|
||||||
|
|
||||||
|
initializationStatus.update { response.lceContent() }
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
initializationStatus.update { e.lceError() }
|
||||||
|
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getInitializationStatus(userWalletId: UserWalletId): InitializationStatusFlow {
|
||||||
|
return getInitializationStatusInternal(userWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInitializationStatusInternal(userWalletId: UserWalletId): InitializationStatusFlow {
|
||||||
|
val initializationStatus = initializationStatuses.value.get(key = userWalletId)
|
||||||
|
|
||||||
|
if (initializationStatus != null) return initializationStatus
|
||||||
|
|
||||||
|
val default: InitializationStatusFlow = MutableStateFlow(value = lceLoading())
|
||||||
|
|
||||||
|
initializationStatuses.update {
|
||||||
|
it.toMutableMap().apply {
|
||||||
|
put(key = userWalletId, value = default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.datasource.exchangeservice.swap
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.express.models.response.Asset
|
||||||
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
|
import com.tangem.domain.core.lce.Lce
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swap service loader
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
interface SwapServiceLoader {
|
||||||
|
|
||||||
|
/** Update service using [userWalletId] and [userTokens] */
|
||||||
|
suspend fun update(userWalletId: UserWalletId, userTokens: UserTokensResponse)
|
||||||
|
|
||||||
|
/** Get initialization status by [userWalletId] */
|
||||||
|
fun getInitializationStatus(userWalletId: UserWalletId): StateFlow<Lce<Throwable, List<Asset>>>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.tangem.datasource.local.onramp
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||||
|
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||||
|
import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator
|
||||||
|
|
||||||
|
internal class DefaultOnrampPaymentMethodsStore(
|
||||||
|
dataStore: StringKeyDataStore<List<PaymentMethodDTO>>,
|
||||||
|
) : OnrampPaymentMethodsStore, StringKeyDataStoreDecorator<String, List<PaymentMethodDTO>>(dataStore) {
|
||||||
|
|
||||||
|
override fun provideStringKey(key: String): String = key
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.tangem.datasource.local.onramp
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||||
|
|
||||||
|
interface OnrampPaymentMethodsStore {
|
||||||
|
|
||||||
|
suspend fun getSyncOrNull(key: String): List<PaymentMethodDTO>?
|
||||||
|
|
||||||
|
suspend fun store(key: String, value: List<PaymentMethodDTO>)
|
||||||
|
|
||||||
|
suspend fun contains(key: String): Boolean
|
||||||
|
}
|
||||||
|
|
@ -53,10 +53,6 @@ object PreferencesKeys {
|
||||||
|
|
||||||
val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") }
|
val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") }
|
||||||
|
|
||||||
val IS_WALLET_TRAVALA_PROMO_SHOWN_KEY by lazy {
|
|
||||||
booleanPreferencesKey(name = "isWalletTravalaPromoShown")
|
|
||||||
}
|
|
||||||
|
|
||||||
val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") }
|
val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") }
|
||||||
|
|
||||||
val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") }
|
val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") }
|
||||||
|
|
@ -103,6 +99,12 @@ object PreferencesKeys {
|
||||||
|
|
||||||
val SHOULD_SHOW_RING_PROMO_KEY by lazy { booleanPreferencesKey(name = "shouldShowRingPromo") }
|
val SHOULD_SHOW_RING_PROMO_KEY by lazy { booleanPreferencesKey(name = "shouldShowRingPromo") }
|
||||||
|
|
||||||
|
val ONRAMP_DEFAULT_CURRENCY by lazy { stringPreferencesKey(name = "onrampDefaultCurrency") }
|
||||||
|
|
||||||
|
val ONRAMP_DEFAULT_COUNTRY by lazy { stringPreferencesKey(name = "onrampDefaultCountry") }
|
||||||
|
|
||||||
|
val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") }
|
||||||
|
|
||||||
// region Permission
|
// region Permission
|
||||||
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")
|
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,4 +150,12 @@ suspend inline fun <reified T> AppPreferencesStore.getObjectSetSync(key: Prefere
|
||||||
?.get(key)
|
?.get(key)
|
||||||
?.let(adapter::fromJson)
|
?.let(adapter::fromJson)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get flow of set of [T] by string [key], or empty if data is not found */
|
||||||
|
inline fun <reified T> AppPreferencesStore.getObjectSet(key: Preferences.Key<String>): Flow<Set<T>> {
|
||||||
|
val adapter = moshi.adapter<Set<T>>(Types.newParameterizedType(Set::class.java, T::class.java))
|
||||||
|
return data.map {
|
||||||
|
it[key]?.let(adapter::fromJson) ?: emptySet()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,10 +7,6 @@
|
||||||
"name": "WC_SOLANA_TX_SIGN_ENABLED",
|
"name": "WC_SOLANA_TX_SIGN_ENABLED",
|
||||||
"version": "5.18.0"
|
"version": "5.18.0"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "STAKING_ENABLED",
|
|
||||||
"version": "5.15.0"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "IS_ETHEREUM_EIP_1559_ENABLED",
|
"name": "IS_ETHEREUM_EIP_1559_ENABLED",
|
||||||
"version": "5.17.0"
|
"version": "5.17.0"
|
||||||
|
|
@ -25,7 +21,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "MAIN_ACTION_BUTTONS_ENABLED",
|
"name": "MAIN_ACTION_BUTTONS_ENABLED",
|
||||||
"version": "undefined"
|
"version": "5.19.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
|
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
|
||||||
|
|
|
||||||
|
|
@ -908,6 +908,7 @@
|
||||||
<string name="twins_recreate_warning">Diese Aktion ist unumkehrbar. Du hast keinen Zugriff mehr auf die alte Wallet.</string>
|
<string name="twins_recreate_warning">Diese Aktion ist unumkehrbar. Du hast keinen Zugriff mehr auf die alte Wallet.</string>
|
||||||
<string name="twins_scan_twin_with_number">Tippe auf die Doppelkarte oder Ring mit der Nummer %s und entferne sie erst am Ende des Vorgangs.</string>
|
<string name="twins_scan_twin_with_number">Tippe auf die Doppelkarte oder Ring mit der Nummer %s und entferne sie erst am Ende des Vorgangs.</string>
|
||||||
<string name="unlock_wallet_description_full">Verwende %s oder scanne eine Karte oder Ring, um Zugriff auf deine Wallet zu erhalten.</string>
|
<string name="unlock_wallet_description_full">Verwende %s oder scanne eine Karte oder Ring, um Zugriff auf deine Wallet zu erhalten.</string>
|
||||||
|
<string name="unsupported_wc_version">Verbindung fehlgeschlagen: Diese dApp verwendet Wallet Connect Version 1.0, die nicht unterstützt wird. Bitte stelle sicher, dass die dApp Wallet Connect Version 2.0 unterstützt, um eine erfolgreiche Verbindung herzustellen.</string>
|
||||||
<string name="user_push_notification_agreement_argument_one">Bleib auf dem Laufenden mit den neuesten Funktionen und Neuigkeiten</string>
|
<string name="user_push_notification_agreement_argument_one">Bleib auf dem Laufenden mit den neuesten Funktionen und Neuigkeiten</string>
|
||||||
<string name="user_push_notification_agreement_argument_two">Sei der Erste, der von neuen Aktionen erfährt</string>
|
<string name="user_push_notification_agreement_argument_two">Sei der Erste, der von neuen Aktionen erfährt</string>
|
||||||
<string name="user_push_notification_agreement_header">Möchtest du Push-Benachrichtigungen verwenden?</string>
|
<string name="user_push_notification_agreement_header">Möchtest du Push-Benachrichtigungen verwenden?</string>
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,9 @@
|
||||||
<string name="express_provider_not_available">No disponible para este par</string>
|
<string name="express_provider_not_available">No disponible para este par</string>
|
||||||
<string name="express_provider_permission_needed">Permiso requerido</string>
|
<string name="express_provider_permission_needed">Permiso requerido</string>
|
||||||
<string name="express_provider_recommended">Recomendado</string>
|
<string name="express_provider_recommended">Recomendado</string>
|
||||||
|
<string name="express_status_bought">Comprado %s</string>
|
||||||
|
<string name="express_status_buying">Comprando %s</string>
|
||||||
|
<string name="express_status_buying_active">Comprando %s...</string>
|
||||||
<string name="express_token_list_empty_search">No se encontraron fichas. Por favor intenta con otra solicitud</string>
|
<string name="express_token_list_empty_search">No se encontraron fichas. Por favor intenta con otra solicitud</string>
|
||||||
<string name="express_transaction_id">ID : %s</string>
|
<string name="express_transaction_id">ID : %s</string>
|
||||||
<string name="express_transaction_id_copied">ID de transacción copiado</string>
|
<string name="express_transaction_id_copied">ID de transacción copiado</string>
|
||||||
|
|
@ -907,6 +910,7 @@
|
||||||
<string name="twins_recreate_warning">Esta acción es irreversible. No tendrá acceso la billetera antigua.</string>
|
<string name="twins_recreate_warning">Esta acción es irreversible. No tendrá acceso la billetera antigua.</string>
|
||||||
<string name="twins_scan_twin_with_number">Toque la tarjeta gemela con el número %s y no la retira hasta el final de la operación</string>
|
<string name="twins_scan_twin_with_number">Toque la tarjeta gemela con el número %s y no la retira hasta el final de la operación</string>
|
||||||
<string name="unlock_wallet_description_full">Use %s o escanee una tarjeta/anillo para tener acceso a su billetera</string>
|
<string name="unlock_wallet_description_full">Use %s o escanee una tarjeta/anillo para tener acceso a su billetera</string>
|
||||||
|
<string name="unsupported_wc_version">Error de conexión: Esta dApp utiliza la versión 1.0 de Wallet Connect, que no es compatible. Asegúrese de que la dApp sea compatible con la versión 2.0 de Wallet Connect para conectarse correctamente.</string>
|
||||||
<string name="user_push_notification_agreement_argument_one">Manténgase actualizado con las últimas funciones y noticias</string>
|
<string name="user_push_notification_agreement_argument_one">Manténgase actualizado con las últimas funciones y noticias</string>
|
||||||
<string name="user_push_notification_agreement_argument_two">Sea el primero en enterarte de nuevas promociones</string>
|
<string name="user_push_notification_agreement_argument_two">Sea el primero en enterarte de nuevas promociones</string>
|
||||||
<string name="user_push_notification_agreement_header">¿Quiere utilizar\nnotificaciones push?</string>
|
<string name="user_push_notification_agreement_header">¿Quiere utilizar\nnotificaciones push?</string>
|
||||||
|
|
@ -1006,6 +1010,9 @@
|
||||||
<string name="warning_hedera_missing_token_association_message_brief">Este token debe estar asociado con su cuenta de Hedera antes de poder recibirlo.</string>
|
<string name="warning_hedera_missing_token_association_message_brief">Este token debe estar asociado con su cuenta de Hedera antes de poder recibirlo.</string>
|
||||||
<string name="warning_hedera_missing_token_association_title">Asocie su token</string>
|
<string name="warning_hedera_missing_token_association_title">Asocie su token</string>
|
||||||
<string name="warning_hedera_token_association_not_enough_hbar_message">No hay suficiente %s. Recargue su cuenta de Hedera para asociar este token</string>
|
<string name="warning_hedera_token_association_not_enough_hbar_message">No hay suficiente %s. Recargue su cuenta de Hedera para asociar este token</string>
|
||||||
|
<string name="warning_kaspa_unfinished_token_transaction_discard_message">¿Está seguro(a) de que desea cancelar la transacción? No podrá volver a intentar realizarla.</string>
|
||||||
|
<string name="warning_kaspa_unfinished_token_transaction_message">Su transacción con un importe de %1$s %2$s no se ha completado. Puede volver a intentarlo para completarla.</string>
|
||||||
|
<string name="warning_kaspa_unfinished_token_transaction_title">Tiene una transacción sin terminar</string>
|
||||||
<string name="warning_low_signatures_message">Solo quedan %s firmas en esta tarjeta. Deba retirar todos sus fondos.</string>
|
<string name="warning_low_signatures_message">Solo quedan %s firmas en esta tarjeta. Deba retirar todos sus fondos.</string>
|
||||||
<string name="warning_low_signatures_title">Recuento de firmas bajo</string>
|
<string name="warning_low_signatures_title">Recuento de firmas bajo</string>
|
||||||
<string name="warning_manage_tokens_legacy_derivation_message">Los tokens en diferentes redes pueden tener direcciones diferentes. Verifique que su dirección coincida con la red cuando transfieras fondos.</string>
|
<string name="warning_manage_tokens_legacy_derivation_message">Los tokens en diferentes redes pueden tener direcciones diferentes. Verifique que su dirección coincida con la red cuando transfieras fondos.</string>
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,7 @@
|
||||||
<string name="common_terms_of_use">Conditions d\'utilisation</string>
|
<string name="common_terms_of_use">Conditions d\'utilisation</string>
|
||||||
<string name="common_today">Aujourd\'hui</string>
|
<string name="common_today">Aujourd\'hui</string>
|
||||||
<string name="common_transaction_failed">La transaction a échoué</string>
|
<string name="common_transaction_failed">La transaction a échoué</string>
|
||||||
|
<string name="common_transaction_status">Statut de la transaction</string>
|
||||||
<string name="common_transactions">Transactions</string>
|
<string name="common_transactions">Transactions</string>
|
||||||
<string name="common_transfer">Transfert</string>
|
<string name="common_transfer">Transfert</string>
|
||||||
<string name="common_understand">Je comprends</string>
|
<string name="common_understand">Je comprends</string>
|
||||||
|
|
@ -187,6 +188,7 @@
|
||||||
<string name="common_unreachable">Inaccessible</string>
|
<string name="common_unreachable">Inaccessible</string>
|
||||||
<string name="common_unstake">Unstakez</string>
|
<string name="common_unstake">Unstakez</string>
|
||||||
<string name="common_week">semaine</string>
|
<string name="common_week">semaine</string>
|
||||||
|
<string name="common_with">avec</string>
|
||||||
<string name="common_yes">Oui</string>
|
<string name="common_yes">Oui</string>
|
||||||
<string name="contract_address_copied_message">Adresse du contrat copiée !</string>
|
<string name="contract_address_copied_message">Adresse du contrat copiée !</string>
|
||||||
<string name="currency_subtitle_expanded">Réseaux disponibles</string>
|
<string name="currency_subtitle_expanded">Réseaux disponibles</string>
|
||||||
|
|
@ -292,6 +294,9 @@
|
||||||
<string name="express_provider_not_available">Indisponible pour cette paire</string>
|
<string name="express_provider_not_available">Indisponible pour cette paire</string>
|
||||||
<string name="express_provider_permission_needed">Permission requise</string>
|
<string name="express_provider_permission_needed">Permission requise</string>
|
||||||
<string name="express_provider_recommended">Recommandé</string>
|
<string name="express_provider_recommended">Recommandé</string>
|
||||||
|
<string name="express_status_bought">Acheté %s</string>
|
||||||
|
<string name="express_status_buying">Achat en cours %s</string>
|
||||||
|
<string name="express_status_buying_active">Achat en cours %s...</string>
|
||||||
<string name="express_token_list_empty_search">Aucun jeton trouvé. Veuillez essayer une autre demande</string>
|
<string name="express_token_list_empty_search">Aucun jeton trouvé. Veuillez essayer une autre demande</string>
|
||||||
<string name="express_transaction_id">ID : %s</string>
|
<string name="express_transaction_id">ID : %s</string>
|
||||||
<string name="express_transaction_id_copied">ID de transaction copié</string>
|
<string name="express_transaction_id_copied">ID de transaction copié</string>
|
||||||
|
|
@ -427,6 +432,7 @@
|
||||||
<string name="markets_token_details_fully_diluted_valuation_description">La valeur théorique totale d\'une crypto-monnaie si toutes les pièces qui pourraient exister étaient en circulation, y compris celles qui ne circulent pas actuellement</string>
|
<string name="markets_token_details_fully_diluted_valuation_description">La valeur théorique totale d\'une crypto-monnaie si toutes les pièces qui pourraient exister étaient en circulation, y compris celles qui ne circulent pas actuellement</string>
|
||||||
<string name="markets_token_details_fully_diluted_valuation_full">Valorisation entièrement diluée</string>
|
<string name="markets_token_details_fully_diluted_valuation_full">Valorisation entièrement diluée</string>
|
||||||
<string name="markets_token_details_genesis_date">Date de la Genesis</string>
|
<string name="markets_token_details_genesis_date">Date de la Genesis</string>
|
||||||
|
<string name="markets_token_details_genesis_date_description">Vide</string>
|
||||||
<string name="markets_token_details_high">Haut</string>
|
<string name="markets_token_details_high">Haut</string>
|
||||||
<string name="markets_token_details_holders">Détenteurs</string>
|
<string name="markets_token_details_holders">Détenteurs</string>
|
||||||
<string name="markets_token_details_holders_description">L\'évolution du nombre de détenteurs de jetons au cours d\'une période donnée</string>
|
<string name="markets_token_details_holders_description">L\'évolution du nombre de détenteurs de jetons au cours d\'une période donnée</string>
|
||||||
|
|
@ -437,6 +443,7 @@
|
||||||
<string name="markets_token_details_liquidity_description">Le changement dans la quantité de liquidité disponible pour le jeton pendant la période spécifiée</string>
|
<string name="markets_token_details_liquidity_description">Le changement dans la quantité de liquidité disponible pour le jeton pendant la période spécifiée</string>
|
||||||
<string name="markets_token_details_liquidity_full">Liquidité</string>
|
<string name="markets_token_details_liquidity_full">Liquidité</string>
|
||||||
<string name="markets_token_details_liquidity_index">Indice de liquidité</string>
|
<string name="markets_token_details_liquidity_index">Indice de liquidité</string>
|
||||||
|
<string name="markets_token_details_liquidity_index_description">Vide</string>
|
||||||
<string name="markets_token_details_listed_on">Listé sur</string>
|
<string name="markets_token_details_listed_on">Listé sur</string>
|
||||||
<string name="markets_token_details_low">Faible</string>
|
<string name="markets_token_details_low">Faible</string>
|
||||||
<string name="markets_token_details_market_capitalization">Cap. boursière</string>
|
<string name="markets_token_details_market_capitalization">Cap. boursière</string>
|
||||||
|
|
@ -453,6 +460,7 @@
|
||||||
<string name="markets_token_details_price_performance">Performance des prix</string>
|
<string name="markets_token_details_price_performance">Performance des prix</string>
|
||||||
<string name="markets_token_details_repository">Dépôt</string>
|
<string name="markets_token_details_repository">Dépôt</string>
|
||||||
<string name="markets_token_details_security_score">Score de sécurité</string>
|
<string name="markets_token_details_security_score">Score de sécurité</string>
|
||||||
|
<string name="markets_token_details_security_score_description">Le score de sécurité d\'un jeton est une mesure qui évalue le niveau de sécurité d\'une blockchain ou d\'un jeton en fonction de divers facteurs. Ceci est compilé à partir des sources répertoriées ci-dessous.</string>
|
||||||
<string name="markets_token_details_social">Social</string>
|
<string name="markets_token_details_social">Social</string>
|
||||||
<string name="markets_token_details_total_supply">Approvisionnement total</string>
|
<string name="markets_token_details_total_supply">Approvisionnement total</string>
|
||||||
<string name="markets_token_details_total_supply_description">Le nombre maximal de pièces ou de jetons pouvant exister pour une crypto-monnaie particulière</string>
|
<string name="markets_token_details_total_supply_description">Le nombre maximal de pièces ou de jetons pouvant exister pour une crypto-monnaie particulière</string>
|
||||||
|
|
@ -509,12 +517,12 @@
|
||||||
<string name="onboarding_navbar_title_creating_backup">Sauvegarde en cours</string>
|
<string name="onboarding_navbar_title_creating_backup">Sauvegarde en cours</string>
|
||||||
<string name="onboarding_seed_button_read_more">En savoir plus sur les seed phrases</string>
|
<string name="onboarding_seed_button_read_more">En savoir plus sur les seed phrases</string>
|
||||||
<plurals name="onboarding_seed_generate_message_words_count">
|
<plurals name="onboarding_seed_generate_message_words_count">
|
||||||
<item quantity="one"></item>
|
<item quantity="one">Vide</item>
|
||||||
<item quantity="other">Écrivez ces %d mots dans l\'ordre indiqué ci-dessous et conservez-les dans un endroit sûr et secret.</item>
|
<item quantity="other">Écrivez ces %d mots dans l\'ordre indiqué ci-dessous et conservez-les dans un endroit sûr et secret.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="onboarding_seed_generate_title">Votre seed phrase</string>
|
<string name="onboarding_seed_generate_title">Votre seed phrase</string>
|
||||||
<plurals name="onboarding_seed_generate_words_count">
|
<plurals name="onboarding_seed_generate_words_count">
|
||||||
<item quantity="one"></item>
|
<item quantity="one">Vide</item>
|
||||||
<item quantity="other">%d mots</item>
|
<item quantity="other">%d mots</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="onboarding_seed_import_message">Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous</string>
|
<string name="onboarding_seed_import_message">Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous</string>
|
||||||
|
|
@ -566,12 +574,15 @@
|
||||||
<string name="onramp_min_amount_restriction">Le montant à acheter doit être au moins %s</string>
|
<string name="onramp_min_amount_restriction">Le montant à acheter doit être au moins %s</string>
|
||||||
<string name="onramp_no_available_providers">Aucun fournisseur disponible pour cette devise</string>
|
<string name="onramp_no_available_providers">Aucun fournisseur disponible pour cette devise</string>
|
||||||
<string name="onramp_pay_with">Payer avec</string>
|
<string name="onramp_pay_with">Payer avec</string>
|
||||||
|
<string name="onramp_redirecting_to_provider_subtitle">Vous pourrez finaliser votre transaction sur le fournisseur tiers, %s</string>
|
||||||
|
<string name="onramp_redirecting_to_provider_title">Redirection vers %s...</string>
|
||||||
<string name="onramp_residency_bottomsheet_country_not_supported">Nos services ne sont pas disponibles dans ce pays</string>
|
<string name="onramp_residency_bottomsheet_country_not_supported">Nos services ne sont pas disponibles dans ce pays</string>
|
||||||
<string name="onramp_residency_bottomsheet_country_subtitle">Modifiez-le ou confirmez-le</string>
|
<string name="onramp_residency_bottomsheet_country_subtitle">Modifiez-le ou confirmez-le</string>
|
||||||
<string name="onramp_residency_bottomsheet_title">Votre résidence a été identifiée comme</string>
|
<string name="onramp_residency_bottomsheet_title">Votre résidence a été identifiée comme</string>
|
||||||
<string name="onramp_settings_residence">Résidence</string>
|
<string name="onramp_settings_residence">Résidence</string>
|
||||||
<string name="onramp_settings_residence_description">Veuillez sélectionner le bon pays pour garantir des options de paiement et des services précis.</string>
|
<string name="onramp_settings_residence_description">Veuillez sélectionner le bon pays pour garantir des options de paiement et des services précis.</string>
|
||||||
<string name="onramp_settings_title">Paramètres</string>
|
<string name="onramp_settings_title">Paramètres</string>
|
||||||
|
<string name="onramp_transaction_status_footer_text">Vous pouvez vérifier l\'état de la transaction à partir de la page du jeton</string>
|
||||||
<string name="onramp_via">Via</string>
|
<string name="onramp_via">Via</string>
|
||||||
<string name="organize_tokens_group">Grouper</string>
|
<string name="organize_tokens_group">Grouper</string>
|
||||||
<string name="organize_tokens_sort_by_balance">Par solde</string>
|
<string name="organize_tokens_sort_by_balance">Par solde</string>
|
||||||
|
|
@ -899,6 +910,7 @@
|
||||||
<string name="twins_recreate_warning">Cette action est irréversible. Vous n\'aurez plus accès à l\'ancien portefeuille.</string>
|
<string name="twins_recreate_warning">Cette action est irréversible. Vous n\'aurez plus accès à l\'ancien portefeuille.</string>
|
||||||
<string name="twins_scan_twin_with_number">Appuyez sur la carte jumelle avec le numéro %s et ne la retirez pas jusqu\'à la fin de l\'opération</string>
|
<string name="twins_scan_twin_with_number">Appuyez sur la carte jumelle avec le numéro %s et ne la retirez pas jusqu\'à la fin de l\'opération</string>
|
||||||
<string name="unlock_wallet_description_full">Utilisez %s ou scannez une carte/bague pour avoir accès à votre portefeuille</string>
|
<string name="unlock_wallet_description_full">Utilisez %s ou scannez une carte/bague pour avoir accès à votre portefeuille</string>
|
||||||
|
<string name="unsupported_wc_version">Échec de la connexion : Cette dApp utilise Wallet Connect version1.0, qui n\'est pas prise en charge. Veuillez vous assurer que la dApp prend en charge Wallet Connect version2.0 pour réussir la connexion.</string>
|
||||||
<string name="user_push_notification_agreement_argument_one">Restez à jour avec les dernières fonctionnalités et actualités</string>
|
<string name="user_push_notification_agreement_argument_one">Restez à jour avec les dernières fonctionnalités et actualités</string>
|
||||||
<string name="user_push_notification_agreement_argument_two">Soyez le premier informé des nouvelles promotions</string>
|
<string name="user_push_notification_agreement_argument_two">Soyez le premier informé des nouvelles promotions</string>
|
||||||
<string name="user_push_notification_agreement_header">Souhaitez-vous utiliser les\nnotifications push?</string>
|
<string name="user_push_notification_agreement_header">Souhaitez-vous utiliser les\nnotifications push?</string>
|
||||||
|
|
@ -950,6 +962,9 @@
|
||||||
<string name="wallet_network_group_title">%s réseau</string>
|
<string name="wallet_network_group_title">%s réseau</string>
|
||||||
<string name="wallet_notification_address_copied">L\'adresse a été copiée avec succès</string>
|
<string name="wallet_notification_address_copied">L\'adresse a été copiée avec succès</string>
|
||||||
<string name="wallet_notification_no_internet">Pas de connexion internet</string>
|
<string name="wallet_notification_no_internet">Pas de connexion internet</string>
|
||||||
|
<string name="wallet_promo_banner_button_title">Obtenez-le maintenant avec 10 %% de réduction</string>
|
||||||
|
<string name="wallet_promo_banner_description">Accédez à plus de 13 000 cryptomonnaies. Achetez, vendez, échangez et stakez en un seul clic.\nAssociez jusqu\'à trois cartes pour une sauvegarde.</string>
|
||||||
|
<string name="wallet_promo_banner_title">Découvrez le Portefeuille Tangem</string>
|
||||||
<string name="wallet_settings_title">Paramètres du portefeuille</string>
|
<string name="wallet_settings_title">Paramètres du portefeuille</string>
|
||||||
<string name="wallet_title">Tangem</string>
|
<string name="wallet_title">Tangem</string>
|
||||||
<string name="warning_access_denied_message">Utilisez %s ou scannez une carte/bague pour déverrouiller l\'accès à votre portefeuille</string>
|
<string name="warning_access_denied_message">Utilisez %s ou scannez une carte/bague pour déverrouiller l\'accès à votre portefeuille</string>
|
||||||
|
|
@ -995,6 +1010,9 @@
|
||||||
<string name="warning_hedera_missing_token_association_message_brief">Ce jeton doit être associé à votre compte Hedera avant que vous puissiez le recevoir</string>
|
<string name="warning_hedera_missing_token_association_message_brief">Ce jeton doit être associé à votre compte Hedera avant que vous puissiez le recevoir</string>
|
||||||
<string name="warning_hedera_missing_token_association_title">Associez votre jeton</string>
|
<string name="warning_hedera_missing_token_association_title">Associez votre jeton</string>
|
||||||
<string name="warning_hedera_token_association_not_enough_hbar_message">%s insuffisant. Renflouez votre compte Hedera pour associer ce jeton</string>
|
<string name="warning_hedera_token_association_not_enough_hbar_message">%s insuffisant. Renflouez votre compte Hedera pour associer ce jeton</string>
|
||||||
|
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Êtes-vous sûr(e) de vouloir annuler la transaction ? Vous ne pourrez plus réessayer.</string>
|
||||||
|
<string name="warning_kaspa_unfinished_token_transaction_message">Votre transaction d\'un montant de %1$s %2$s n\'a pas été finalisée. Vous pouvez réessayer plus tard pour la finaliser.</string>
|
||||||
|
<string name="warning_kaspa_unfinished_token_transaction_title">Vous avez une transaction inachevée</string>
|
||||||
<string name="warning_low_signatures_message">Il ne reste que %s signatures sur cette carte. Vous devez retirer tous vos fonds.</string>
|
<string name="warning_low_signatures_message">Il ne reste que %s signatures sur cette carte. Vous devez retirer tous vos fonds.</string>
|
||||||
<string name="warning_low_signatures_title">Faible nombre de signatures</string>
|
<string name="warning_low_signatures_title">Faible nombre de signatures</string>
|
||||||
<string name="warning_manage_tokens_legacy_derivation_message">Les jetons sur différents réseaux peuvent avoir des adresses différentes. Vérifiez bien que votre adresse correspond au réseau lorsque vous transférez des fonds.</string>
|
<string name="warning_manage_tokens_legacy_derivation_message">Les jetons sur différents réseaux peuvent avoir des adresses différentes. Vérifiez bien que votre adresse correspond au réseau lorsque vous transférez des fonds.</string>
|
||||||
|
|
|
||||||
|
|
@ -898,6 +898,7 @@
|
||||||
<string name="twins_recreate_warning">この操作は元に戻せません。古いウォレットにはアクセスできなくなります。</string>
|
<string name="twins_recreate_warning">この操作は元に戻せません。古いウォレットにはアクセスできなくなります。</string>
|
||||||
<string name="twins_scan_twin_with_number">番号%sのツインカードをタップし、操作が終了するまで取り外さないでください。</string>
|
<string name="twins_scan_twin_with_number">番号%sのツインカードをタップし、操作が終了するまで取り外さないでください。</string>
|
||||||
<string name="unlock_wallet_description_full">%sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください</string>
|
<string name="unlock_wallet_description_full">%sを使用するか、カード / リングをスキャンしてウォレットにアクセスしてください</string>
|
||||||
|
<string name="unsupported_wc_version">接続に失敗しました:このdAppは、サポートされていないWallet Connectバージョン1.0を使用しています。正常に接続するには、dAppがWallet Connectバージョン2.0をサポートしていることを確認してください。</string>
|
||||||
<string name="user_push_notification_agreement_argument_one">最新の機能とニュースをお届けします</string>
|
<string name="user_push_notification_agreement_argument_one">最新の機能とニュースをお届けします</string>
|
||||||
<string name="user_push_notification_agreement_argument_two">新しいプロモーション情報をいち早く入手しましょう</string>
|
<string name="user_push_notification_agreement_argument_two">新しいプロモーション情報をいち早く入手しましょう</string>
|
||||||
<string name="user_push_notification_agreement_header">プッシュ通知を使用しますか?</string>
|
<string name="user_push_notification_agreement_header">プッシュ通知を使用しますか?</string>
|
||||||
|
|
|
||||||
|
|
@ -913,6 +913,7 @@
|
||||||
<string name="twins_recreate_warning">Это действие необратимо. У вас не будет доступа к старому кошельку.</string>
|
<string name="twins_recreate_warning">Это действие необратимо. У вас не будет доступа к старому кошельку.</string>
|
||||||
<string name="twins_scan_twin_with_number">Приложите twin-карту с номером %s и не убирайте до окончания операции</string>
|
<string name="twins_scan_twin_with_number">Приложите twin-карту с номером %s и не убирайте до окончания операции</string>
|
||||||
<string name="unlock_wallet_description_full">Используйте %s или отсканируйте карту/кольцо, чтобы получить доступ к своему кошельку</string>
|
<string name="unlock_wallet_description_full">Используйте %s или отсканируйте карту/кольцо, чтобы получить доступ к своему кошельку</string>
|
||||||
|
<string name="unsupported_wc_version">Соединение не удалось: это dApp использует Wallet Connect версии 1.0, которая не поддерживается. Убедитесь, что dApp поддерживает Wallet Connect версии 2.0 для успешного подключения.</string>
|
||||||
<string name="user_push_notification_agreement_argument_one">Будьте в курсе новых функций и новостей</string>
|
<string name="user_push_notification_agreement_argument_one">Будьте в курсе новых функций и новостей</string>
|
||||||
<string name="user_push_notification_agreement_argument_two">Узнавайте первым о новых акциях</string>
|
<string name="user_push_notification_agreement_argument_two">Узнавайте первым о новых акциях</string>
|
||||||
<string name="user_push_notification_agreement_header">Хотите использовать Push-уведомления?</string>
|
<string name="user_push_notification_agreement_header">Хотите использовать Push-уведомления?</string>
|
||||||
|
|
|
||||||
|
|
@ -927,6 +927,7 @@
|
||||||
<string name="twins_recreate_warning">Ця дія є незворотною. Ви не матимете доступу до старого гаманця.</string>
|
<string name="twins_recreate_warning">Ця дія є незворотною. Ви не матимете доступу до старого гаманця.</string>
|
||||||
<string name="twins_scan_twin_with_number">Прикладіть twin-картку з номером %s та не прибирайте її до завершення операції</string>
|
<string name="twins_scan_twin_with_number">Прикладіть twin-картку з номером %s та не прибирайте її до завершення операції</string>
|
||||||
<string name="unlock_wallet_description_full">Використовуйте %s або відскануйте картку/кільце, щоб отримати доступ до свого гаманця</string>
|
<string name="unlock_wallet_description_full">Використовуйте %s або відскануйте картку/кільце, щоб отримати доступ до свого гаманця</string>
|
||||||
|
<string name="unsupported_wc_version">Не вдалося встановити з\'єднання: Цей dApp використовує Wallet Connect версії 1.0, яка не підтримується. Будь ласка, переконайтеся, що dApp підтримує Wallet Connect версії 2.0 для успішного підключення.</string>
|
||||||
<string name="user_push_notification_agreement_argument_one">Будьте в курсі останніх функцій та новин</string>
|
<string name="user_push_notification_agreement_argument_one">Будьте в курсі останніх функцій та новин</string>
|
||||||
<string name="user_push_notification_agreement_argument_two">Дізнавайтеся першими про нові акції</string>
|
<string name="user_push_notification_agreement_argument_two">Дізнавайтеся першими про нові акції</string>
|
||||||
<string name="user_push_notification_agreement_header">Бажаєте використовувати Push-повідомлення?</string>
|
<string name="user_push_notification_agreement_header">Бажаєте використовувати Push-повідомлення?</string>
|
||||||
|
|
|
||||||
|
|
@ -908,6 +908,7 @@
|
||||||
<string name="twins_recreate_warning">This action is irreversible. You will not have access to the old wallet.</string>
|
<string name="twins_recreate_warning">This action is irreversible. You will not have access to the old wallet.</string>
|
||||||
<string name="twins_scan_twin_with_number">Tap the twin card with number %s and do not remove until the end of the operation</string>
|
<string name="twins_scan_twin_with_number">Tap the twin card with number %s and do not remove until the end of the operation</string>
|
||||||
<string name="unlock_wallet_description_full">Use %s or scan a card/ring to have access to your wallet</string>
|
<string name="unlock_wallet_description_full">Use %s or scan a card/ring to have access to your wallet</string>
|
||||||
|
<string name="unsupported_wc_version">Connection failed: This dApp uses Wallet Connect version 1.0, which is not supported. Please ensure the dApp supports Wallet Connect version 2.0 to connect successfully.</string>
|
||||||
<string name="user_push_notification_agreement_argument_one">Stay up to date with the latest features and news</string>
|
<string name="user_push_notification_agreement_argument_one">Stay up to date with the latest features and news</string>
|
||||||
<string name="user_push_notification_agreement_argument_two">Be the first to know about new promotions</string>
|
<string name="user_push_notification_agreement_argument_two">Be the first to know about new promotions</string>
|
||||||
<string name="user_push_notification_agreement_header">Would you like to use\nPush-notifications?</string>
|
<string name="user_push_notification_agreement_header">Would you like to use\nPush-notifications?</string>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import android.content.res.Configuration
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||||
|
|
@ -26,6 +27,7 @@ fun AppBarWithBackButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
text: String? = null,
|
text: String? = null,
|
||||||
@DrawableRes iconRes: Int? = null,
|
@DrawableRes iconRes: Int? = null,
|
||||||
|
containerColor: Color = Color.Transparent,
|
||||||
) {
|
) {
|
||||||
TangemTopAppBar(
|
TangemTopAppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -34,6 +36,7 @@ fun AppBarWithBackButton(
|
||||||
iconRes = iconRes ?: R.drawable.ic_back_24,
|
iconRes = iconRes ?: R.drawable.ic_back_24,
|
||||||
onIconClicked = onBackClick,
|
onIconClicked = onBackClick,
|
||||||
),
|
),
|
||||||
|
containerColor = containerColor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,4 +23,5 @@ data class ActionButtonConfig(
|
||||||
val onLongClick: (() -> TextReference?)? = null,
|
val onLongClick: (() -> TextReference?)? = null,
|
||||||
val enabled: Boolean = true,
|
val enabled: Boolean = true,
|
||||||
val dimContent: Boolean = false,
|
val dimContent: Boolean = false,
|
||||||
|
val isInProgress: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
@ -8,8 +8,9 @@ import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
|
@ -77,7 +78,6 @@ fun ActionButton(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Button(
|
private fun Button(
|
||||||
config: ActionButtonConfig,
|
config: ActionButtonConfig,
|
||||||
|
|
@ -89,12 +89,33 @@ private fun Button(
|
||||||
targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled,
|
targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled,
|
||||||
label = "Update background color",
|
label = "Update background color",
|
||||||
)
|
)
|
||||||
val context = LocalContext.current
|
|
||||||
Row(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.heightIn(min = TangemTheme.dimens.size36)
|
.heightIn(min = TangemTheme.dimens.size36)
|
||||||
.clip(shape)
|
.clip(shape)
|
||||||
.background(color = backgroundColor)
|
.background(color = backgroundColor),
|
||||||
|
) {
|
||||||
|
Content(config = config)
|
||||||
|
|
||||||
|
if (config.isInProgress) {
|
||||||
|
Loading(
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
modifier = Modifier
|
||||||
|
.matchParentSize()
|
||||||
|
.align(alignment = Alignment.Center),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun Content(config: ActionButtonConfig) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
enabled = config.enabled,
|
enabled = config.enabled,
|
||||||
onClick = config.onClick,
|
onClick = config.onClick,
|
||||||
|
|
@ -149,6 +170,19 @@ private fun Button(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Loading(backgroundColor: Color, modifier: Modifier = Modifier) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier.background(color = backgroundColor),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||||
|
color = TangemTheme.colors.icon.accent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(group = "RoundedActionButton", showBackground = true)
|
@Preview(group = "RoundedActionButton", showBackground = true)
|
||||||
@Preview(group = "RoundedActionButton", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(group = "RoundedActionButton", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -188,5 +222,12 @@ private class ActionStateProvider : CollectionPreviewParameterProvider<ActionBut
|
||||||
enabled = false,
|
enabled = false,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
),
|
),
|
||||||
|
ActionButtonConfig(
|
||||||
|
text = TextReference.Str(value = "Loading"),
|
||||||
|
iconResId = R.drawable.ic_arrow_down_24,
|
||||||
|
enabled = false,
|
||||||
|
onClick = {},
|
||||||
|
isInProgress = true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -5,8 +5,11 @@ import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20,15 +23,16 @@ import com.tangem.core.ui.res.TangemTheme
|
||||||
@Composable
|
@Composable
|
||||||
fun FooterContainer(
|
fun FooterContainer(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
footer: String? = null,
|
footer: TextReference? = null,
|
||||||
footerTopPadding: Dp = TangemTheme.dimens.spacing8,
|
footerTopPadding: Dp = TangemTheme.dimens.spacing8,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
content()
|
content()
|
||||||
AnimatedVisibility(visible = footer != null) {
|
AnimatedVisibility(visible = footer != null) {
|
||||||
|
val footerWrapped = remember(this) { requireNotNull(footer) }
|
||||||
Text(
|
Text(
|
||||||
text = footer.orEmpty(),
|
text = footerWrapped.resolveReference(),
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = TangemTheme.colors.text.tertiary,
|
color = TangemTheme.colors.text.tertiary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import com.tangem.core.ui.components.token.internal.*
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.res.TangemColorPalette
|
import com.tangem.core.ui.res.TangemColorPalette
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
|
@ -145,14 +146,16 @@ private fun Modifier.tokenClickable(state: TokenItemState): Modifier = composed
|
||||||
is TokenItemState.Unreachable,
|
is TokenItemState.Unreachable,
|
||||||
-> {
|
-> {
|
||||||
val onClick = state.onItemClick
|
val onClick = state.onItemClick
|
||||||
val onLongClick = state.onItemLongClick?.let { rememberHapticFeedback(state = state, onAction = it) }
|
val onLongClick = state.onItemLongClick?.let {
|
||||||
|
rememberHapticFeedback(state = state, onAction = { it(state) })
|
||||||
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
onClick == null && onLongClick == null -> this
|
onClick == null && onLongClick == null -> this
|
||||||
onClick == null && onLongClick != null -> combinedClickable(onClick = {}, onLongClick = onLongClick)
|
onClick == null && onLongClick != null -> combinedClickable(onClick = {}, onLongClick = onLongClick)
|
||||||
onClick != null && onLongClick == null -> combinedClickable(onClick = onClick)
|
onClick != null && onLongClick == null -> combinedClickable(onClick = { onClick(state) })
|
||||||
onClick != null && onLongClick != null -> {
|
onClick != null && onLongClick != null -> {
|
||||||
combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
combinedClickable(onClick = { onClick(state) }, onLongClick = onLongClick)
|
||||||
}
|
}
|
||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
@ -456,7 +459,7 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
||||||
tokenItemVisibleState.copy(
|
tokenItemVisibleState.copy(
|
||||||
iconState = coinIconState.copy(showCustomBadge = true),
|
iconState = coinIconState.copy(showCustomBadge = true),
|
||||||
titleState = TokenItemState.TitleState.Content(
|
titleState = TokenItemState.TitleState.Content(
|
||||||
text = "PolygonPolygonPolygonPolygonPolygonPolygon",
|
text = stringReference(value = "PolygonPolygonPolygonPolygonPolygonPolygon"),
|
||||||
hasPending = true,
|
hasPending = true,
|
||||||
),
|
),
|
||||||
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
||||||
|
|
@ -473,41 +476,41 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
||||||
TokenItemState.Unreachable(
|
TokenItemState.Unreachable(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
iconState = tokenIconState,
|
iconState = tokenIconState,
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
onItemClick = {},
|
onItemClick = {},
|
||||||
onItemLongClick = {},
|
onItemLongClick = {},
|
||||||
),
|
),
|
||||||
TokenItemState.Unreachable(
|
TokenItemState.Unreachable(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
iconState = tokenIconState,
|
iconState = tokenIconState,
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
subtitleState = TokenItemState.SubtitleState.TextContent("Token"),
|
subtitleState = TokenItemState.SubtitleState.TextContent(stringReference(value = "Token")),
|
||||||
onItemClick = {},
|
onItemClick = {},
|
||||||
onItemLongClick = {},
|
onItemLongClick = {},
|
||||||
),
|
),
|
||||||
TokenItemState.NoAddress(
|
TokenItemState.NoAddress(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
iconState = tokenIconState,
|
iconState = tokenIconState,
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
onItemLongClick = {},
|
onItemLongClick = {},
|
||||||
),
|
),
|
||||||
TokenItemState.NoAddress(
|
TokenItemState.NoAddress(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
iconState = tokenIconState,
|
iconState = tokenIconState,
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
subtitleState = TokenItemState.SubtitleState.TextContent("Token"),
|
subtitleState = TokenItemState.SubtitleState.TextContent(value = stringReference(value = "Token")),
|
||||||
onItemLongClick = {},
|
onItemLongClick = {},
|
||||||
),
|
),
|
||||||
TokenItemState.Draggable(
|
TokenItemState.Draggable(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
iconState = tokenIconState,
|
iconState = tokenIconState,
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "3 172,14 $"),
|
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "3 172,14 $"),
|
||||||
),
|
),
|
||||||
TokenItemState.Content(
|
TokenItemState.Content(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
iconState = tokenIconState,
|
iconState = tokenIconState,
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = false),
|
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = false),
|
||||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"),
|
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"),
|
||||||
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
|
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
|
||||||
|
|
@ -521,7 +524,7 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
||||||
TokenItemState.Content(
|
TokenItemState.Content(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
iconState = tokenIconState,
|
iconState = tokenIconState,
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = false),
|
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = false),
|
||||||
subtitle2State = TokenItemState.Subtitle2State.LabelContent(
|
subtitle2State = TokenItemState.Subtitle2State.LabelContent(
|
||||||
auditLabelUM = AuditLabelUM(
|
auditLabelUM = AuditLabelUM(
|
||||||
|
|
@ -540,21 +543,21 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
||||||
TokenItemState.Loading(
|
TokenItemState.Loading(
|
||||||
id = "Loading#1",
|
id = "Loading#1",
|
||||||
iconState = customTokenIconState.copy(isGrayscale = true),
|
iconState = customTokenIconState.copy(isGrayscale = true),
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
),
|
),
|
||||||
tokenItemVisibleState.copy(
|
tokenItemVisibleState.copy(
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon testnet"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon testnet")),
|
||||||
iconState = tokenIconState.copy(isGrayscale = true),
|
iconState = tokenIconState.copy(isGrayscale = true),
|
||||||
),
|
),
|
||||||
tokenItemVisibleState.copy(
|
tokenItemVisibleState.copy(
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
iconState = customTokenIconState.copy(
|
iconState = customTokenIconState.copy(
|
||||||
tint = TangemColorPalette.White,
|
tint = TangemColorPalette.White,
|
||||||
background = TangemColorPalette.Black,
|
background = TangemColorPalette.Black,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tokenItemVisibleState.copy(
|
tokenItemVisibleState.copy(
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||||
iconState = customTokenIconState.copy(isGrayscale = true),
|
iconState = customTokenIconState.copy(isGrayscale = true),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -592,7 +595,10 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
||||||
TokenItemState.Content(
|
TokenItemState.Content(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
iconState = coinIconState,
|
iconState = coinIconState,
|
||||||
titleState = TokenItemState.TitleState.Content(text = "Polygon", hasPending = true),
|
titleState = TokenItemState.TitleState.Content(
|
||||||
|
text = stringReference(value = "Polygon"),
|
||||||
|
hasPending = true,
|
||||||
|
),
|
||||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = true),
|
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = true),
|
||||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"),
|
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"),
|
||||||
subtitleState = TokenItemState.SubtitleState.Unknown,
|
subtitleState = TokenItemState.SubtitleState.Unknown,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import com.tangem.core.ui.components.RectangleShimmer
|
||||||
import com.tangem.core.ui.components.SpacerW4
|
import com.tangem.core.ui.components.SpacerW4
|
||||||
import com.tangem.core.ui.components.SpacerW6
|
import com.tangem.core.ui.components.SpacerW6
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||||
|
|
@ -38,7 +40,7 @@ internal fun TokenPrice(state: TokenPriceState?, modifier: Modifier = Modifier)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is TokenPriceState.TextContent -> {
|
is TokenPriceState.TextContent -> {
|
||||||
PriceText(text = state.value, modifier = modifier, isAvailable = state.isAvailable)
|
PriceText(text = state.value.resolveReference(), modifier = modifier, isAvailable = state.isAvailable)
|
||||||
}
|
}
|
||||||
is TokenPriceState.Unknown -> {
|
is TokenPriceState.Unknown -> {
|
||||||
PriceText(text = DASH_SIGN, modifier = modifier)
|
PriceText(text = DASH_SIGN, modifier = modifier)
|
||||||
|
|
@ -158,7 +160,7 @@ private class TokenPriceChangeStateProvider : CollectionPreviewParameterProvider
|
||||||
priceChangePercent = "2.5%",
|
priceChangePercent = "2.5%",
|
||||||
type = PriceChangeType.NEUTRAL,
|
type = PriceChangeType.NEUTRAL,
|
||||||
),
|
),
|
||||||
TokenPriceState.TextContent(value = "Subtitle", isAvailable = true),
|
TokenPriceState.TextContent(value = stringReference(value = "Subtitle"), isAvailable = true),
|
||||||
TokenPriceState.Unknown,
|
TokenPriceState.Unknown,
|
||||||
TokenPriceState.Loading,
|
TokenPriceState.Loading,
|
||||||
TokenPriceState.Locked,
|
TokenPriceState.Locked,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.RectangleShimmer
|
import com.tangem.core.ui.components.RectangleShimmer
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as TokenTitleState
|
import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as TokenTitleState
|
||||||
|
|
||||||
|
|
@ -46,7 +47,7 @@ private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Mo
|
||||||
* So we need to use [weight] to avoid displacement.
|
* So we need to use [weight] to avoid displacement.
|
||||||
*/
|
*/
|
||||||
CurrencyNameText(
|
CurrencyNameText(
|
||||||
name = state.text,
|
name = state.text.resolveReference(),
|
||||||
isAvailable = state.isAvailable,
|
isAvailable = state.isAvailable,
|
||||||
modifier = Modifier.weight(weight = 1f, fill = false),
|
modifier = Modifier.weight(weight = 1f, fill = false),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
|
||||||
import com.tangem.core.ui.components.audits.AuditLabelUM
|
import com.tangem.core.ui.components.audits.AuditLabelUM
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
|
||||||
/** TokenItem component state */
|
/** TokenItem component state */
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
@ -31,10 +32,10 @@ sealed class TokenItemState {
|
||||||
abstract val subtitle2State: Subtitle2State?
|
abstract val subtitle2State: Subtitle2State?
|
||||||
|
|
||||||
/** Callback which will be called when an item is clicked */
|
/** Callback which will be called when an item is clicked */
|
||||||
abstract val onItemClick: (() -> Unit)?
|
abstract val onItemClick: ((TokenItemState) -> Unit)?
|
||||||
|
|
||||||
/** Callback which will be called when an item is long clicked */
|
/** Callback which will be called when an item is long clicked */
|
||||||
abstract val onItemLongClick: (() -> Unit)?
|
abstract val onItemLongClick: ((TokenItemState) -> Unit)?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loading token state
|
* Loading token state
|
||||||
|
|
@ -52,8 +53,8 @@ sealed class TokenItemState {
|
||||||
) : TokenItemState() {
|
) : TokenItemState() {
|
||||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading
|
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading
|
||||||
override val subtitle2State: Subtitle2State = Subtitle2State.Loading
|
override val subtitle2State: Subtitle2State = Subtitle2State.Loading
|
||||||
override val onItemClick: (() -> Unit)? = null
|
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||||
override val onItemLongClick: (() -> Unit)? = null
|
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -67,8 +68,8 @@ sealed class TokenItemState {
|
||||||
override val subtitleState: SubtitleState = SubtitleState.Locked
|
override val subtitleState: SubtitleState = SubtitleState.Locked
|
||||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Locked
|
override val fiatAmountState: FiatAmountState = FiatAmountState.Locked
|
||||||
override val subtitle2State: Subtitle2State = Subtitle2State.Locked
|
override val subtitle2State: Subtitle2State = Subtitle2State.Locked
|
||||||
override val onItemClick: (() -> Unit)? = null
|
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||||
override val onItemLongClick: (() -> Unit)? = null
|
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,8 +91,8 @@ sealed class TokenItemState {
|
||||||
override val subtitleState: SubtitleState,
|
override val subtitleState: SubtitleState,
|
||||||
override val fiatAmountState: FiatAmountState,
|
override val fiatAmountState: FiatAmountState,
|
||||||
override val subtitle2State: Subtitle2State,
|
override val subtitle2State: Subtitle2State,
|
||||||
override val onItemClick: (() -> Unit)?,
|
override val onItemClick: ((TokenItemState) -> Unit)?,
|
||||||
override val onItemLongClick: (() -> Unit)?,
|
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||||
) : TokenItemState()
|
) : TokenItemState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -110,8 +111,8 @@ sealed class TokenItemState {
|
||||||
) : TokenItemState() {
|
) : TokenItemState() {
|
||||||
override val subtitleState: SubtitleState? = null
|
override val subtitleState: SubtitleState? = null
|
||||||
override val fiatAmountState: FiatAmountState? = null
|
override val fiatAmountState: FiatAmountState? = null
|
||||||
override val onItemClick: (() -> Unit)? = null
|
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||||
override val onItemLongClick: (() -> Unit)? = null
|
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -129,8 +130,8 @@ sealed class TokenItemState {
|
||||||
override val iconState: CurrencyIconState,
|
override val iconState: CurrencyIconState,
|
||||||
override val titleState: TitleState,
|
override val titleState: TitleState,
|
||||||
override val subtitleState: SubtitleState? = null,
|
override val subtitleState: SubtitleState? = null,
|
||||||
override val onItemClick: (() -> Unit)?,
|
override val onItemClick: ((TokenItemState) -> Unit)?,
|
||||||
override val onItemLongClick: (() -> Unit)?,
|
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||||
) : TokenItemState() {
|
) : TokenItemState() {
|
||||||
override val fiatAmountState: FiatAmountState? = null
|
override val fiatAmountState: FiatAmountState? = null
|
||||||
override val subtitle2State: Subtitle2State? = null
|
override val subtitle2State: Subtitle2State? = null
|
||||||
|
|
@ -150,18 +151,18 @@ sealed class TokenItemState {
|
||||||
override val iconState: CurrencyIconState,
|
override val iconState: CurrencyIconState,
|
||||||
override val titleState: TitleState,
|
override val titleState: TitleState,
|
||||||
override val subtitleState: SubtitleState? = null,
|
override val subtitleState: SubtitleState? = null,
|
||||||
override val onItemLongClick: (() -> Unit)?,
|
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||||
) : TokenItemState() {
|
) : TokenItemState() {
|
||||||
override val fiatAmountState: FiatAmountState? = null
|
override val fiatAmountState: FiatAmountState? = null
|
||||||
override val subtitle2State: Subtitle2State? = null
|
override val subtitle2State: Subtitle2State? = null
|
||||||
override val onItemClick: (() -> Unit)? = null
|
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
sealed class TitleState {
|
sealed class TitleState {
|
||||||
|
|
||||||
data class Content(
|
data class Content(
|
||||||
val text: String,
|
val text: TextReference,
|
||||||
val hasPending: Boolean = false,
|
val hasPending: Boolean = false,
|
||||||
val isAvailable: Boolean = true,
|
val isAvailable: Boolean = true,
|
||||||
) : TitleState()
|
) : TitleState()
|
||||||
|
|
@ -180,7 +181,7 @@ sealed class TokenItemState {
|
||||||
val type: PriceChangeType,
|
val type: PriceChangeType,
|
||||||
) : SubtitleState()
|
) : SubtitleState()
|
||||||
|
|
||||||
data class TextContent(val value: String, val isAvailable: Boolean = true) : SubtitleState()
|
data class TextContent(val value: TextReference, val isAvailable: Boolean = true) : SubtitleState()
|
||||||
|
|
||||||
data object Unknown : SubtitleState()
|
data object Unknown : SubtitleState()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.components.fields.SearchBar
|
import com.tangem.core.ui.components.fields.SearchBar
|
||||||
import com.tangem.core.ui.components.token.TokenItem
|
import com.tangem.core.ui.components.token.TokenItem
|
||||||
|
import com.tangem.core.ui.components.tokenlist.internal.GroupTitleItem
|
||||||
import com.tangem.core.ui.components.tokenlist.internal.NetworkTitleItem
|
import com.tangem.core.ui.components.tokenlist.internal.NetworkTitleItem
|
||||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
|
@ -25,6 +26,9 @@ fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: M
|
||||||
is TokensListItemUM.NetworkGroupTitle -> {
|
is TokensListItemUM.NetworkGroupTitle -> {
|
||||||
NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifier)
|
NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifier)
|
||||||
}
|
}
|
||||||
|
is TokensListItemUM.GroupTitle -> {
|
||||||
|
GroupTitleItem(state, modifier)
|
||||||
|
}
|
||||||
is TokensListItemUM.Token -> {
|
is TokensListItemUM.Token -> {
|
||||||
TokenItem(
|
TokenItem(
|
||||||
state = state.state,
|
state = state.state,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.tangem.core.ui.components.tokenlist.internal
|
||||||
|
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.tangem.core.ui.components.rows.NetworkTitle
|
||||||
|
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group title item
|
||||||
|
*
|
||||||
|
* @param state state
|
||||||
|
* @param modifier modifier
|
||||||
|
*
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun GroupTitleItem(state: TokensListItemUM.GroupTitle, modifier: Modifier = Modifier) {
|
||||||
|
NetworkTitle(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = state.text.resolveReference(),
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -78,9 +77,7 @@ private fun BaseNetworkTitleItem(
|
||||||
@Composable
|
@Composable
|
||||||
private fun DraggableIcon(reorderableTokenListState: ReorderableLazyListState) {
|
private fun DraggableIcon(reorderableTokenListState: ReorderableLazyListState) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.detectReorder(reorderableTokenListState),
|
||||||
.size(TangemTheme.dimens.size32)
|
|
||||||
.detectReorder(reorderableTokenListState),
|
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,17 @@ sealed interface TokensListItemUM {
|
||||||
* @property id id
|
* @property id id
|
||||||
* @property name network group name
|
* @property name network group name
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use GroupTitle instead") // TODO: [REDACTED_JIRA]
|
||||||
data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemUM
|
data class NetworkGroupTitle(override val id: Int, val name: TextReference) : TokensListItemUM
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group title
|
||||||
|
*
|
||||||
|
* @property id id
|
||||||
|
* @property text title value
|
||||||
|
*/
|
||||||
|
data class GroupTitle(override val id: Any, val text: TextReference) : TokensListItemUM
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token item
|
* Token item
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ fun getActiveIconRes(blockchainId: String): Int {
|
||||||
"casper", "casper/test" -> R.drawable.img_casper_22
|
"casper", "casper/test" -> R.drawable.img_casper_22
|
||||||
"xodex" -> R.drawable.img_xodex_22
|
"xodex" -> R.drawable.img_xodex_22
|
||||||
"canxium" -> R.drawable.img_canxium_22
|
"canxium" -> R.drawable.img_canxium_22
|
||||||
|
"chiliz", "chiliz/test" -> R.drawable.img_chiliz_22
|
||||||
else -> R.drawable.ic_alert_24
|
else -> R.drawable.ic_alert_24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -162,6 +163,7 @@ fun getActiveIconResByCoinId(coinId: String): Int {
|
||||||
"casper-network" -> R.drawable.img_casper_22
|
"casper-network" -> R.drawable.img_casper_22
|
||||||
"xodex" -> R.drawable.img_xodex_22
|
"xodex" -> R.drawable.img_xodex_22
|
||||||
"canxium" -> R.drawable.img_canxium_22
|
"canxium" -> R.drawable.img_canxium_22
|
||||||
|
"chiliz", "chiliz/test" -> R.drawable.img_chiliz_22
|
||||||
else -> R.drawable.ic_alert_24
|
else -> R.drawable.ic_alert_24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -245,6 +247,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
|
||||||
"casper", "casper/test" -> R.drawable.ic_casper_22
|
"casper", "casper/test" -> R.drawable.ic_casper_22
|
||||||
"xodex" -> R.drawable.ic_xodex_22
|
"xodex" -> R.drawable.ic_xodex_22
|
||||||
"canxium" -> R.drawable.ic_canxium_22
|
"canxium" -> R.drawable.ic_canxium_22
|
||||||
|
"chiliz", "chiliz/test" -> R.drawable.ic_chiliz_22
|
||||||
else -> R.drawable.ic_alert_24
|
else -> R.drawable.ic_alert_24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ data class TangemDimens internal constructor(
|
||||||
val radius26: Dp = 26.dp,
|
val radius26: Dp = 26.dp,
|
||||||
val radius28: Dp = 28.dp,
|
val radius28: Dp = 28.dp,
|
||||||
val radius36: Dp = 36.dp,
|
val radius36: Dp = 36.dp,
|
||||||
|
val radius40: Dp = 40.dp,
|
||||||
// endregion Radius
|
// endregion Radius
|
||||||
// region Size
|
// region Size
|
||||||
val size0: Dp = 0.dp,
|
val size0: Dp = 0.dp,
|
||||||
|
|
|
||||||
9
core/ui/src/main/res/drawable/ic_chiliz_22.xml
Normal file
9
core/ui/src/main/res/drawable/ic_chiliz_22.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="22dp"
|
||||||
|
android:height="22dp"
|
||||||
|
android:viewportWidth="22"
|
||||||
|
android:viewportHeight="22">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M9.807,4.515L7.378,6.014C7.33,6.043 7.266,6.029 7.237,5.981L7.094,5.749C6.911,5.454 7.003,5.066 7.298,4.884L8.245,4.304C8.406,4.204 8.432,3.98 8.297,3.846L7.505,3.114C7.257,2.869 7.256,2.47 7.502,2.223L7.694,2.029C7.733,1.99 7.798,1.99 7.838,2.029L9.866,3.991C10.02,4.144 10.02,4.394 9.807,4.515ZM9.09,11.632C9.686,12.288 9.921,13.194 9.722,14.056L9.724,14.055C9.724,14.055 9.607,15.192 8.941,17.83C8.755,18.564 9.472,19.211 10.18,18.942C15,17.111 14.995,12.218 14.995,12.218C15.138,9.067 11.943,6.016 11.025,5.169C10.884,5.04 10.672,5.021 10.51,5.124L8,6.728C6.913,7.423 6.702,8.924 7.555,9.893L9.09,11.632ZM8.589,7.638C9.223,7.236 9.967,6.764 10.351,6.522V6.523C10.468,6.449 10.621,6.463 10.721,6.558C15.663,11.206 13.294,14.712 11.604,16.171C11.206,16.514 10.602,16.156 10.718,15.644L10.988,14.465C11.296,13.133 10.931,11.737 10.012,10.726L8.408,8.872C8.079,8.491 8.165,7.908 8.589,7.638Z" />
|
||||||
|
</vector>
|
||||||
20
core/ui/src/main/res/drawable/img_chiliz_22.xml
Normal file
20
core/ui/src/main/res/drawable/img_chiliz_22.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="22dp"
|
||||||
|
android:height="22dp"
|
||||||
|
android:viewportWidth="22"
|
||||||
|
android:viewportHeight="22">
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h22v22h-22z"
|
||||||
|
android:fillColor="#FF1256"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M9.807,4.515L7.378,6.014C7.33,6.043 7.266,6.029 7.237,5.981L7.094,5.749C6.911,5.454 7.003,5.066 7.298,4.884L8.245,4.304C8.406,4.204 8.432,3.98 8.297,3.846L7.505,3.114C7.257,2.869 7.256,2.47 7.502,2.223L7.694,2.029C7.733,1.99 7.798,1.99 7.838,2.029L9.866,3.991C10.02,4.144 10.02,4.394 9.807,4.515ZM9.09,11.632C9.686,12.288 9.921,13.194 9.722,14.056L9.724,14.055C9.724,14.055 9.607,15.192 8.941,17.83C8.755,18.564 9.472,19.211 10.18,18.942C15,17.111 14.995,12.218 14.995,12.218C15.138,9.067 11.943,6.016 11.025,5.169C10.884,5.04 10.672,5.021 10.51,5.124L8,6.728C6.913,7.423 6.702,8.924 7.555,9.893L9.09,11.632ZM8.589,7.638C9.223,7.236 9.967,6.764 10.351,6.522V6.523C10.468,6.449 10.621,6.463 10.721,6.558C15.663,11.206 13.294,14.712 11.604,16.171C11.206,16.514 10.602,16.156 10.718,15.644L10.988,14.465C11.296,13.133 10.931,11.737 10.012,10.726L8.408,8.872C8.079,8.491 8.165,7.908 8.589,7.638Z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 441 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 409 KiB |
|
|
@ -14,15 +14,11 @@ import com.tangem.blockchain.common.Token as SdkToken
|
||||||
|
|
||||||
class ResponseCryptoCurrenciesFactory {
|
class ResponseCryptoCurrenciesFactory {
|
||||||
|
|
||||||
fun createCurrency(
|
fun createCurrency(currencyId: String, response: UserTokensResponse, scanResponse: ScanResponse): CryptoCurrency {
|
||||||
currencyId: CryptoCurrency.ID,
|
|
||||||
response: UserTokensResponse,
|
|
||||||
scanResponse: ScanResponse,
|
|
||||||
): CryptoCurrency {
|
|
||||||
return response.tokens
|
return response.tokens
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.mapNotNull { createCurrency(it, scanResponse) }
|
.mapNotNull { createCurrency(it, scanResponse) }
|
||||||
.first { it.id == currencyId }
|
.first { it.id.value == currencyId }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCurrencies(response: UserTokensResponse, scanResponse: ScanResponse): List<CryptoCurrency> {
|
fun createCurrencies(response: UserTokensResponse, scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ dependencies {
|
||||||
|
|
||||||
// region Others dependencies
|
// region Others dependencies
|
||||||
implementation(deps.kotlin.coroutines)
|
implementation(deps.kotlin.coroutines)
|
||||||
|
implementation(deps.jodatime)
|
||||||
implementation(deps.moshi)
|
implementation(deps.moshi)
|
||||||
implementation(deps.moshi.kotlin)
|
implementation(deps.moshi.kotlin)
|
||||||
implementation(deps.timber)
|
implementation(deps.timber)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.tangem.blockchainsdk.compatibility.applyL2Compatibility
|
||||||
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
|
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
|
||||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import com.tangem.data.common.cache.CacheRegistry
|
||||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||||
import com.tangem.data.common.currency.getNetwork
|
import com.tangem.data.common.currency.getNetwork
|
||||||
import com.tangem.data.common.utils.retryOnError
|
import com.tangem.data.common.utils.retryOnError
|
||||||
|
|
@ -13,8 +14,10 @@ import com.tangem.data.markets.converters.*
|
||||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||||
import com.tangem.datasource.api.common.response.getOrThrow
|
import com.tangem.datasource.api.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||||
|
import com.tangem.datasource.api.markets.models.response.TokenMarketExchangesResponse
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi.Companion.marketsQuoteFields
|
import com.tangem.datasource.api.tangemTech.TangemTechApi.Companion.marketsQuoteFields
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.markets.*
|
import com.tangem.domain.markets.*
|
||||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||||
|
|
@ -27,12 +30,15 @@ import kotlinx.coroutines.withContext
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
internal class DefaultMarketsTokenRepository(
|
internal class DefaultMarketsTokenRepository(
|
||||||
private val marketsApi: TangemTechMarketsApi,
|
private val marketsApi: TangemTechMarketsApi,
|
||||||
private val tangemTechApi: TangemTechApi,
|
private val tangemTechApi: TangemTechApi,
|
||||||
private val userWalletsStore: UserWalletsStore,
|
private val userWalletsStore: UserWalletsStore,
|
||||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
private val cacheRegistry: CacheRegistry,
|
||||||
|
private val tokenExchangesStore: RuntimeStateStore<List<TokenMarketExchangesResponse.Exchange>>,
|
||||||
) : MarketsTokenRepository {
|
) : MarketsTokenRepository {
|
||||||
|
|
||||||
private fun createTokenMarketsFetcher(firstBatchSize: Int, nextBatchSize: Int) = LimitOffsetBatchFetcher(
|
private fun createTokenMarketsFetcher(firstBatchSize: Int, nextBatchSize: Int) = LimitOffsetBatchFetcher(
|
||||||
|
|
@ -253,9 +259,13 @@ internal class DefaultMarketsTokenRepository(
|
||||||
|
|
||||||
override suspend fun getTokenExchanges(tokenId: String): List<TokenMarketExchange> {
|
override suspend fun getTokenExchanges(tokenId: String): List<TokenMarketExchange> {
|
||||||
return withContext(dispatcherProvider.io) {
|
return withContext(dispatcherProvider.io) {
|
||||||
val response = marketsApi.getCoinExchanges(coinId = tokenId).getOrThrow()
|
cacheRegistry.invokeOnExpire(key = "coins/$tokenId/exchanges", skipCache = false) {
|
||||||
|
val response = marketsApi.getCoinExchanges(coinId = tokenId).getOrThrow()
|
||||||
|
|
||||||
TokenMarketExchangeConverter.convertList(input = response.exchanges)
|
tokenExchangesStore.store(value = response.exchanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenMarketExchangeConverter.convertList(input = tokenExchangesStore.get().value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package com.tangem.data.markets.converters
|
package com.tangem.data.markets.converters
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||||
import com.tangem.blockchainsdk.utils.isSupportedInApp
|
import com.tangem.blockchainsdk.utils.isSupportedInApp
|
||||||
import com.tangem.datasource.api.markets.models.response.TokenMarketInfoResponse
|
import com.tangem.datasource.api.markets.models.response.TokenMarketInfoResponse
|
||||||
|
import com.tangem.domain.markets.BuildConfig
|
||||||
import com.tangem.domain.markets.TokenMarketInfo
|
import com.tangem.domain.markets.TokenMarketInfo
|
||||||
import com.tangem.domain.markets.TokenQuotes
|
import com.tangem.domain.markets.TokenQuotes
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
@ -11,6 +13,9 @@ import java.math.BigDecimal
|
||||||
|
|
||||||
internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, TokenMarketInfo> {
|
internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, TokenMarketInfo> {
|
||||||
|
|
||||||
|
private const val PROD_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/security_provider/"
|
||||||
|
private const val DEV_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api.dev/security_provider/"
|
||||||
|
|
||||||
override fun convert(value: TokenMarketInfoResponse): TokenMarketInfo {
|
override fun convert(value: TokenMarketInfoResponse): TokenMarketInfo {
|
||||||
return with(value) {
|
return with(value) {
|
||||||
TokenMarketInfo(
|
TokenMarketInfo(
|
||||||
|
|
@ -23,6 +28,7 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
|
||||||
fullDescription = fullDescription,
|
fullDescription = fullDescription,
|
||||||
insights = insights?.convert(),
|
insights = insights?.convert(),
|
||||||
metrics = metrics?.convert(),
|
metrics = metrics?.convert(),
|
||||||
|
securityData = securityData?.convert(),
|
||||||
links = links?.convert(),
|
links = links?.convert(),
|
||||||
pricePerformance = pricePerformance?.convert(),
|
pricePerformance = pricePerformance?.convert(),
|
||||||
exchangesAmount = exchangesAmount,
|
exchangesAmount = exchangesAmount,
|
||||||
|
|
@ -105,6 +111,31 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun TokenMarketInfoResponse.SecurityData.convert(): TokenMarketInfo.SecurityData {
|
||||||
|
return TokenMarketInfo.SecurityData(
|
||||||
|
totalSecurityScore = totalSecurityScore,
|
||||||
|
securityScoreProviderData = providerData.map { it.convert() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TokenMarketInfoResponse.ProviderData.convert(): TokenMarketInfo.SecurityScoreProvider {
|
||||||
|
return TokenMarketInfo.SecurityScoreProvider(
|
||||||
|
providerId = providerId,
|
||||||
|
providerName = providerName,
|
||||||
|
urlData = link?.convertToUrlData(),
|
||||||
|
securityScore = securityScore,
|
||||||
|
lastAuditDate = lastAuditDate,
|
||||||
|
iconUrl = "${getImageHost()}large/$providerId.png",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.convertToUrlData(): TokenMarketInfo.SecurityScoreProvider.UrlData {
|
||||||
|
return TokenMarketInfo.SecurityScoreProvider.UrlData(
|
||||||
|
fullUrl = this,
|
||||||
|
rootHost = this.extractMainDomainOrNull(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun TokenMarketInfoResponse.Links.convert(): TokenMarketInfo.Links {
|
private fun TokenMarketInfoResponse.Links.convert(): TokenMarketInfo.Links {
|
||||||
return TokenMarketInfo.Links(
|
return TokenMarketInfo.Links(
|
||||||
officialLinks = officialLinks?.convert(),
|
officialLinks = officialLinks?.convert(),
|
||||||
|
|
@ -139,4 +170,27 @@ internal object TokenMarketInfoConverter : Converter<TokenMarketInfoResponse, To
|
||||||
high = high,
|
high = high,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.extractMainDomainOrNull(): String? {
|
||||||
|
return runCatching {
|
||||||
|
val uri = Uri.parse(this)
|
||||||
|
val host = uri.host ?: return null
|
||||||
|
|
||||||
|
val parts = host.split(".")
|
||||||
|
|
||||||
|
if (parts.size >= 2) {
|
||||||
|
parts.takeLast(2).joinToString(".")
|
||||||
|
} else {
|
||||||
|
host
|
||||||
|
}
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getImageHost(): String {
|
||||||
|
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||||
|
DEV_IMAGE_HOST
|
||||||
|
} else {
|
||||||
|
PROD_IMAGE_HOST
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package com.tangem.data.markets.di
|
package com.tangem.data.markets.di
|
||||||
|
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
|
import com.tangem.data.common.cache.CacheRegistry
|
||||||
import com.tangem.data.markets.DefaultMarketsTokenRepository
|
import com.tangem.data.markets.DefaultMarketsTokenRepository
|
||||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -25,6 +27,7 @@ internal object MarketsDataModule {
|
||||||
userWalletsStore: UserWalletsStore,
|
userWalletsStore: UserWalletsStore,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
analyticsEventHandler: AnalyticsEventHandler,
|
analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
cacheRegistry: CacheRegistry,
|
||||||
): MarketsTokenRepository {
|
): MarketsTokenRepository {
|
||||||
return DefaultMarketsTokenRepository(
|
return DefaultMarketsTokenRepository(
|
||||||
marketsApi = marketsApi,
|
marketsApi = marketsApi,
|
||||||
|
|
@ -32,6 +35,8 @@ internal object MarketsDataModule {
|
||||||
dispatcherProvider = dispatchers,
|
dispatcherProvider = dispatchers,
|
||||||
userWalletsStore = userWalletsStore,
|
userWalletsStore = userWalletsStore,
|
||||||
analyticsEventHandler = analyticsEventHandler,
|
analyticsEventHandler = analyticsEventHandler,
|
||||||
|
cacheRegistry = cacheRegistry,
|
||||||
|
tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,9 +5,9 @@ import com.tangem.datasource.api.common.response.ApiResponseError
|
||||||
import com.tangem.domain.onramp.model.OnrampError
|
import com.tangem.domain.onramp.model.OnrampError
|
||||||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||||
|
|
||||||
internal class DefaultOnrampErrorResolver(
|
internal class DefaultOnrampErrorResolver : OnrampErrorResolver {
|
||||||
private val onrampErrorConverter: OnrampErrorConverter,
|
|
||||||
) : OnrampErrorResolver {
|
private val onrampErrorConverter = OnrampErrorConverter()
|
||||||
|
|
||||||
override fun resolve(throwable: Throwable): OnrampError {
|
override fun resolve(throwable: Throwable): OnrampError {
|
||||||
return if (throwable is ApiResponseError.HttpException) {
|
return if (throwable is ApiResponseError.HttpException) {
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,42 @@
|
||||||
package com.tangem.data.onramp
|
package com.tangem.data.onramp
|
||||||
|
|
||||||
|
import com.tangem.data.common.api.safeApiCall
|
||||||
import com.tangem.data.onramp.converters.CountryConverter
|
import com.tangem.data.onramp.converters.CountryConverter
|
||||||
import com.tangem.data.onramp.converters.CurrencyConverter
|
import com.tangem.data.onramp.converters.CurrencyConverter
|
||||||
|
import com.tangem.data.onramp.converters.StatusConverter
|
||||||
|
import com.tangem.data.onramp.converters.PaymentMethodConverter
|
||||||
import com.tangem.datasource.api.common.response.getOrThrow
|
import com.tangem.datasource.api.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.onramp.OnrampApi
|
import com.tangem.datasource.api.onramp.OnrampApi
|
||||||
|
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||||
|
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||||
|
import com.tangem.datasource.local.onramp.OnrampPaymentMethodsStore
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
|
import com.tangem.datasource.local.preferences.utils.getObject
|
||||||
|
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||||
|
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||||
import com.tangem.domain.onramp.model.OnrampCountry
|
import com.tangem.domain.onramp.model.OnrampCountry
|
||||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||||
|
import com.tangem.domain.onramp.model.OnrampStatus
|
||||||
|
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
internal class DefaultOnrampRepository(
|
internal class DefaultOnrampRepository(
|
||||||
private val onrampApi: OnrampApi,
|
private val onrampApi: OnrampApi,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
|
private val paymentMethodsStore: OnrampPaymentMethodsStore,
|
||||||
) : OnrampRepository {
|
) : OnrampRepository {
|
||||||
|
|
||||||
private val currencyConverter = CurrencyConverter()
|
private val currencyConverter = CurrencyConverter()
|
||||||
private val countryConverter = CountryConverter()
|
private val countryConverter = CountryConverter(currencyConverter)
|
||||||
|
private val statusConverter = StatusConverter()
|
||||||
|
private val paymentMethodsConverter = PaymentMethodConverter()
|
||||||
|
|
||||||
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
|
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
|
||||||
onrampApi.getCurrencies()
|
onrampApi.getCurrencies()
|
||||||
|
|
@ -29,4 +49,78 @@ internal class DefaultOnrampRepository(
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.map(countryConverter::convert)
|
.map(countryConverter::convert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getCountryByIp(): OnrampCountry = withContext(dispatchers.io) {
|
||||||
|
onrampApi.getCountryByIp()
|
||||||
|
.getOrThrow()
|
||||||
|
.let(countryConverter::convert)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getStatus(txId: String): OnrampStatus = withContext(dispatchers.io) {
|
||||||
|
onrampApi.getStatus(txId)
|
||||||
|
.getOrThrow()
|
||||||
|
.let(statusConverter::convert)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun saveDefaultCurrency(currency: OnrampCurrency) = withContext(dispatchers.io) {
|
||||||
|
appPreferencesStore.storeObject<OnrampCurrencyDTO>(
|
||||||
|
key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY,
|
||||||
|
value = currencyConverter.convertBack(currency),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDefaultCurrencySync(): OnrampCurrency? = withContext(dispatchers.io) {
|
||||||
|
appPreferencesStore
|
||||||
|
.getObjectSyncOrNull<OnrampCurrencyDTO>(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY)
|
||||||
|
?.let(currencyConverter::convert)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDefaultCurrency(): Flow<OnrampCurrency?> {
|
||||||
|
return appPreferencesStore
|
||||||
|
.getObject<OnrampCurrencyDTO>(PreferencesKeys.ONRAMP_DEFAULT_CURRENCY)
|
||||||
|
.map { it?.let(currencyConverter::convert) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun saveDefaultCountry(country: OnrampCountry) = withContext(dispatchers.io) {
|
||||||
|
appPreferencesStore.storeObject<OnrampCountryDTO>(
|
||||||
|
key = PreferencesKeys.ONRAMP_DEFAULT_COUNTRY,
|
||||||
|
value = countryConverter.convertBack(country),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDefaultCountrySync(): OnrampCountry? = withContext(dispatchers.io) {
|
||||||
|
appPreferencesStore
|
||||||
|
.getObjectSyncOrNull<OnrampCountryDTO>(PreferencesKeys.ONRAMP_DEFAULT_COUNTRY)
|
||||||
|
?.let(countryConverter::convert)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDefaultCountry(): Flow<OnrampCountry?> {
|
||||||
|
return appPreferencesStore
|
||||||
|
.getObject<OnrampCountryDTO>(PreferencesKeys.ONRAMP_DEFAULT_COUNTRY)
|
||||||
|
.map { it?.let(countryConverter::convert) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchPaymentMethodsIfAbsent() {
|
||||||
|
if (paymentMethodsStore.contains(PAYMENT_METHODS_KEY)) return
|
||||||
|
|
||||||
|
val response = safeApiCall(
|
||||||
|
call = { onrampApi.getPaymentMethods().bind() },
|
||||||
|
onError = {
|
||||||
|
Timber.w(it, "Unable to fetch onramp payment methods")
|
||||||
|
throw it
|
||||||
|
},
|
||||||
|
)
|
||||||
|
paymentMethodsStore.store(PAYMENT_METHODS_KEY, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPaymentMethods(): List<OnrampPaymentMethod> {
|
||||||
|
val paymentMethods = requireNotNull(paymentMethodsStore.getSyncOrNull(PAYMENT_METHODS_KEY)) {
|
||||||
|
"Onramp payment methods is absent in storage"
|
||||||
|
}
|
||||||
|
return paymentMethodsConverter.convertList(paymentMethods)
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val PAYMENT_METHODS_KEY = "onramp_payment_methods"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.tangem.data.onramp
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
|
import com.tangem.datasource.local.preferences.utils.getObjectSet
|
||||||
|
import com.tangem.datasource.local.preferences.utils.getObjectSetSync
|
||||||
|
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||||
|
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||||
|
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||||
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import com.tangem.utils.extensions.addOrReplace
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
internal class DefaultOnrampTransactionRepository(
|
||||||
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
) : OnrampTransactionRepository {
|
||||||
|
|
||||||
|
override suspend fun storeTransaction(transaction: OnrampTransaction) {
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
appPreferencesStore.editData { mutablePreferences ->
|
||||||
|
val stored = mutablePreferences.getObjectSet<OnrampTransaction>(
|
||||||
|
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||||
|
)
|
||||||
|
val updated = stored?.toMutableSet()
|
||||||
|
?.addOrReplace(transaction) { it.txId == transaction.txId }
|
||||||
|
?: mutableSetOf(transaction)
|
||||||
|
|
||||||
|
mutablePreferences.setObjectSet(
|
||||||
|
key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||||
|
value = updated,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getTransactionById(txId: String): OnrampTransaction? = withContext(dispatchers.io) {
|
||||||
|
val stored = appPreferencesStore.getObjectSetSync<OnrampTransaction>(
|
||||||
|
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
stored.firstOrNull { it.txId == txId }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTransactions(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
cryptoCurrencyId: CryptoCurrency.ID,
|
||||||
|
): Flow<List<OnrampTransaction>> = appPreferencesStore
|
||||||
|
.getObjectSet<OnrampTransaction>(PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY)
|
||||||
|
.map { transactions ->
|
||||||
|
transactions.filter {
|
||||||
|
it.userWalletId == userWalletId && it.toCurrencyId == cryptoCurrencyId.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeTransaction(txId: String) {
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
appPreferencesStore.editData { mutablePreferences ->
|
||||||
|
runCatching {
|
||||||
|
val stored = mutablePreferences.getObjectSet<OnrampTransaction>(
|
||||||
|
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||||
|
)?.toMutableSet()
|
||||||
|
|
||||||
|
stored?.removeIf { it.txId == txId }
|
||||||
|
|
||||||
|
mutablePreferences.setObjectSet(
|
||||||
|
key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
|
||||||
|
value = stored ?: emptySet(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,13 +2,14 @@ package com.tangem.data.onramp.converters
|
||||||
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||||
import com.tangem.domain.onramp.model.OnrampCountry
|
import com.tangem.domain.onramp.model.OnrampCountry
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.TwoWayConverter
|
||||||
|
|
||||||
internal class CountryConverter : Converter<OnrampCountryDTO, OnrampCountry> {
|
internal class CountryConverter(
|
||||||
|
private val currencyConverter: CurrencyConverter,
|
||||||
private val currencyConverter = CurrencyConverter()
|
) : TwoWayConverter<OnrampCountryDTO, OnrampCountry> {
|
||||||
|
|
||||||
override fun convert(value: OnrampCountryDTO) = OnrampCountry(
|
override fun convert(value: OnrampCountryDTO) = OnrampCountry(
|
||||||
|
id = "${value.alpha3}-${value.name}",
|
||||||
name = value.name,
|
name = value.name,
|
||||||
code = value.code,
|
code = value.code,
|
||||||
image = value.image,
|
image = value.image,
|
||||||
|
|
@ -17,4 +18,14 @@ internal class CountryConverter : Converter<OnrampCountryDTO, OnrampCountry> {
|
||||||
defaultCurrency = currencyConverter.convert(value.defaultCurrency),
|
defaultCurrency = currencyConverter.convert(value.defaultCurrency),
|
||||||
onrampAvailable = value.onrampAvailable,
|
onrampAvailable = value.onrampAvailable,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun convertBack(value: OnrampCountry): OnrampCountryDTO = OnrampCountryDTO(
|
||||||
|
name = value.name,
|
||||||
|
code = value.code,
|
||||||
|
image = value.image,
|
||||||
|
alpha3 = value.alpha3,
|
||||||
|
continent = value.continent,
|
||||||
|
defaultCurrency = currencyConverter.convertBack(value.defaultCurrency),
|
||||||
|
onrampAvailable = value.onrampAvailable,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -2,9 +2,9 @@ package com.tangem.data.onramp.converters
|
||||||
|
|
||||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
import com.tangem.datasource.api.onramp.models.response.model.OnrampCurrencyDTO
|
||||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||||
import com.tangem.utils.converter.Converter
|
import com.tangem.utils.converter.TwoWayConverter
|
||||||
|
|
||||||
internal class CurrencyConverter : Converter<OnrampCurrencyDTO, OnrampCurrency> {
|
internal class CurrencyConverter : TwoWayConverter<OnrampCurrencyDTO, OnrampCurrency> {
|
||||||
|
|
||||||
override fun convert(value: OnrampCurrencyDTO) = OnrampCurrency(
|
override fun convert(value: OnrampCurrencyDTO) = OnrampCurrency(
|
||||||
name = value.name,
|
name = value.name,
|
||||||
|
|
@ -12,4 +12,11 @@ internal class CurrencyConverter : Converter<OnrampCurrencyDTO, OnrampCurrency>
|
||||||
image = value.image,
|
image = value.image,
|
||||||
precision = value.precision,
|
precision = value.precision,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override fun convertBack(value: OnrampCurrency): OnrampCurrencyDTO = OnrampCurrencyDTO(
|
||||||
|
name = value.name,
|
||||||
|
code = value.code,
|
||||||
|
image = value.image,
|
||||||
|
precision = value.precision,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tangem.data.onramp.converters
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||||
|
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
|
internal class PaymentMethodConverter : Converter<PaymentMethodDTO, OnrampPaymentMethod> {
|
||||||
|
override fun convert(value: PaymentMethodDTO): OnrampPaymentMethod = OnrampPaymentMethod(
|
||||||
|
id = value.id,
|
||||||
|
name = value.name,
|
||||||
|
imageUrl = value.image,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.tangem.data.onramp.converters
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
|
||||||
|
import com.tangem.domain.onramp.model.OnrampStatus
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
|
internal class StatusConverter : Converter<OnrampStatusResponse, OnrampStatus> {
|
||||||
|
override fun convert(value: OnrampStatusResponse): OnrampStatus {
|
||||||
|
return OnrampStatus(
|
||||||
|
txId = value.txId,
|
||||||
|
providerId = value.providerId,
|
||||||
|
payoutAddress = value.payoutAddress,
|
||||||
|
status = OnrampStatus.Status.valueOf(value.status.name),
|
||||||
|
failReason = value.failReason,
|
||||||
|
externalTxId = value.externalTxId,
|
||||||
|
externalTxUrl = value.externalTxUrl,
|
||||||
|
payoutHash = value.payoutHash,
|
||||||
|
createdAt = value.createdAt,
|
||||||
|
fromCurrencyCode = value.fromCurrencyCode,
|
||||||
|
fromAmount = value.fromAmount,
|
||||||
|
toContractAddress = value.toContractAddress,
|
||||||
|
toNetwork = value.toNetwork,
|
||||||
|
toDecimals = value.toDecimals,
|
||||||
|
toAmount = value.toAmount,
|
||||||
|
toActualAmount = value.toActualAmount,
|
||||||
|
paymentMethod = value.paymentMethod,
|
||||||
|
countryCode = value.countryCode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
package com.tangem.data.onramp.di
|
package com.tangem.data.onramp.di
|
||||||
|
|
||||||
|
import com.tangem.data.onramp.DefaultOnrampErrorResolver
|
||||||
import com.tangem.data.onramp.DefaultOnrampRepository
|
import com.tangem.data.onramp.DefaultOnrampRepository
|
||||||
|
import com.tangem.data.onramp.DefaultOnrampTransactionRepository
|
||||||
import com.tangem.datasource.api.onramp.OnrampApi
|
import com.tangem.datasource.api.onramp.OnrampApi
|
||||||
|
import com.tangem.datasource.local.onramp.OnrampPaymentMethodsStore
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||||
|
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -16,10 +22,35 @@ internal object OnrampDataModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideOnrampRepository(onrampApi: OnrampApi, dispatchers: CoroutineDispatcherProvider): OnrampRepository {
|
fun provideOnrampRepository(
|
||||||
|
onrampApi: OnrampApi,
|
||||||
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
appPreferencesStore: AppPreferencesStore,
|
||||||
|
paymentMethodsStore: OnrampPaymentMethodsStore,
|
||||||
|
): OnrampRepository {
|
||||||
return DefaultOnrampRepository(
|
return DefaultOnrampRepository(
|
||||||
onrampApi = onrampApi,
|
onrampApi = onrampApi,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
paymentMethodsStore = paymentMethodsStore,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideOnrampTransactionRepository(
|
||||||
|
appPreferencesStore: AppPreferencesStore,
|
||||||
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
): OnrampTransactionRepository {
|
||||||
|
return DefaultOnrampTransactionRepository(
|
||||||
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideOnrampErrorResolver(): OnrampErrorResolver {
|
||||||
|
return DefaultOnrampErrorResolver()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -23,15 +23,6 @@ internal class DefaultPromoRepository(
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getTravalaPromoBanner(): PromoBanner? {
|
|
||||||
return runCatching(dispatchers.io) {
|
|
||||||
promoResponseConverter.convert(
|
|
||||||
tangemApi.getPromotionInfo(TRAVALA)
|
|
||||||
.getOrThrow(),
|
|
||||||
)
|
|
||||||
}.getOrNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getOkxPromoBanner(): PromoBanner? {
|
override suspend fun getOkxPromoBanner(): PromoBanner? {
|
||||||
return runCatching(dispatchers.io) {
|
return runCatching(dispatchers.io) {
|
||||||
promoResponseConverter.convert(
|
promoResponseConverter.convert(
|
||||||
|
|
@ -52,7 +43,6 @@ internal class DefaultPromoRepository(
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val CHANGELLY_NAME = "changelly"
|
private const val CHANGELLY_NAME = "changelly"
|
||||||
private const val TRAVALA = "travala"
|
|
||||||
private const val OKX = "okx"
|
private const val OKX = "okx"
|
||||||
private const val RING = "ring"
|
private const val RING = "ring"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY
|
import com.tangem.datasource.local.preferences.PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY
|
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY
|
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_TRAVALA_PROMO_SHOWN_KEY
|
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
|
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
|
||||||
import com.tangem.datasource.local.preferences.utils.get
|
import com.tangem.datasource.local.preferences.utils.get
|
||||||
import com.tangem.datasource.local.preferences.utils.store
|
import com.tangem.datasource.local.preferences.utils.store
|
||||||
|
|
@ -42,17 +41,6 @@ class DefaultPromoSettingsRepository(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isReadyToShowWalletTravalaPromo(): Flow<Boolean> {
|
|
||||||
return appPreferencesStore.get(IS_WALLET_TRAVALA_PROMO_SHOWN_KEY, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun setNeverToShowWalletTravalaPromo() {
|
|
||||||
appPreferencesStore.store(
|
|
||||||
key = IS_WALLET_TRAVALA_PROMO_SHOWN_KEY,
|
|
||||||
value = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow<Boolean> {
|
override fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow<Boolean> {
|
||||||
return combine(
|
return combine(
|
||||||
flow = appPreferencesStore
|
flow = appPreferencesStore
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,6 @@ import com.tangem.domain.tokens.model.Network
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
|
||||||
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import com.tangem.utils.extensions.orZero
|
import com.tangem.utils.extensions.orZero
|
||||||
|
|
@ -68,7 +67,6 @@ internal class DefaultStakingRepository(
|
||||||
private val stakingBalanceStore: StakingBalanceStore,
|
private val stakingBalanceStore: StakingBalanceStore,
|
||||||
private val cacheRegistry: CacheRegistry,
|
private val cacheRegistry: CacheRegistry,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val stakingFeatureToggle: StakingFeatureToggles,
|
|
||||||
private val walletManagersFacade: WalletManagersFacade,
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
moshi: Moshi,
|
moshi: Moshi,
|
||||||
|
|
@ -323,8 +321,6 @@ internal class DefaultStakingRepository(
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
refresh: Boolean,
|
refresh: Boolean,
|
||||||
) = withContext(dispatchers.io) {
|
) = withContext(dispatchers.io) {
|
||||||
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
|
|
||||||
|
|
||||||
cacheRegistry.invokeOnExpire(
|
cacheRegistry.invokeOnExpire(
|
||||||
key = getYieldBalancesKey(userWalletId),
|
key = getYieldBalancesKey(userWalletId),
|
||||||
skipCache = refresh,
|
skipCache = refresh,
|
||||||
|
|
@ -364,30 +360,26 @@ internal class DefaultStakingRepository(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
): Flow<YieldBalance> = channelFlow {
|
): Flow<YieldBalance> = channelFlow {
|
||||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
launch(dispatchers.io) {
|
||||||
send(YieldBalance.Empty)
|
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||||
} else {
|
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||||
launch(dispatchers.io) {
|
?: error("Could not get integrationId")
|
||||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
stakingBalanceStore.get(userWalletId, address, integrationId)
|
||||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
.distinctUntilChanged()
|
||||||
?: error("Could not get integrationId")
|
.collectLatest {
|
||||||
stakingBalanceStore.get(userWalletId, address, integrationId)
|
if (it != null) {
|
||||||
.distinctUntilChanged()
|
send(yieldBalanceConverter.convert(it))
|
||||||
.collectLatest {
|
} else {
|
||||||
if (it != null) {
|
error("No yield balance available for currency ${cryptoCurrency.id.value}")
|
||||||
send(yieldBalanceConverter.convert(it))
|
|
||||||
} else {
|
|
||||||
error("No yield balance available for currency ${cryptoCurrency.id.value}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
withContext(dispatchers.io) {
|
withContext(dispatchers.io) {
|
||||||
fetchSingleYieldBalance(
|
fetchSingleYieldBalance(
|
||||||
userWalletId,
|
userWalletId,
|
||||||
cryptoCurrency,
|
cryptoCurrency,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.cancellable()
|
}.cancellable()
|
||||||
|
|
||||||
|
|
@ -395,21 +387,17 @@ internal class DefaultStakingRepository(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrency: CryptoCurrency,
|
cryptoCurrency: CryptoCurrency,
|
||||||
): YieldBalance = withContext(dispatchers.io) {
|
): YieldBalance = withContext(dispatchers.io) {
|
||||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
|
||||||
YieldBalance.Empty
|
|
||||||
} else {
|
|
||||||
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
|
|
||||||
|
|
||||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||||
|
|
||||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||||
?: error("Could not get integrationId")
|
?: error("Could not get integrationId")
|
||||||
|
|
||||||
val result = stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId)
|
val result = stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId)
|
||||||
?: return@withContext YieldBalance.Error
|
?: return@withContext YieldBalance.Error
|
||||||
|
|
||||||
yieldBalanceConverter.convert(result)
|
yieldBalanceConverter.convert(result)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun fetchMultiYieldBalance(
|
override suspend fun fetchMultiYieldBalance(
|
||||||
|
|
@ -417,8 +405,6 @@ internal class DefaultStakingRepository(
|
||||||
cryptoCurrencies: List<CryptoCurrency>,
|
cryptoCurrencies: List<CryptoCurrency>,
|
||||||
refresh: Boolean,
|
refresh: Boolean,
|
||||||
) = withContext(dispatchers.io) {
|
) = withContext(dispatchers.io) {
|
||||||
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
|
|
||||||
|
|
||||||
cacheRegistry.invokeOnExpire(
|
cacheRegistry.invokeOnExpire(
|
||||||
key = getYieldBalancesKey(userWalletId),
|
key = getYieldBalancesKey(userWalletId),
|
||||||
skipCache = refresh,
|
skipCache = refresh,
|
||||||
|
|
@ -484,19 +470,15 @@ internal class DefaultStakingRepository(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrencies: List<CryptoCurrency>,
|
cryptoCurrencies: List<CryptoCurrency>,
|
||||||
): Flow<YieldBalanceList> = channelFlow {
|
): Flow<YieldBalanceList> = channelFlow {
|
||||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
stakingBalanceStore.get(userWalletId)
|
||||||
send(YieldBalanceList.Empty)
|
.onEach {
|
||||||
} else {
|
val balances = yieldBalanceListConverter.convert(it)
|
||||||
stakingBalanceStore.get(userWalletId)
|
send(balances)
|
||||||
.onEach {
|
|
||||||
val balances = yieldBalanceListConverter.convert(it)
|
|
||||||
send(balances)
|
|
||||||
}
|
|
||||||
.launchIn(scope = this + dispatchers.io)
|
|
||||||
|
|
||||||
withContext(dispatchers.io) {
|
|
||||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false)
|
|
||||||
}
|
}
|
||||||
|
.launchIn(scope = this + dispatchers.io)
|
||||||
|
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -504,13 +486,9 @@ internal class DefaultStakingRepository(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
cryptoCurrencies: List<CryptoCurrency>,
|
cryptoCurrencies: List<CryptoCurrency>,
|
||||||
): YieldBalanceList = withContext(dispatchers.io) {
|
): YieldBalanceList = withContext(dispatchers.io) {
|
||||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
|
||||||
YieldBalanceList.Empty
|
val result = stakingBalanceStore.getSyncOrNull(userWalletId) ?: return@withContext YieldBalanceList.Error
|
||||||
} else {
|
yieldBalanceListConverter.convert(result)
|
||||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
|
|
||||||
val result = stakingBalanceStore.getSyncOrNull(userWalletId) ?: return@withContext YieldBalanceList.Error
|
|
||||||
yieldBalanceListConverter.convert(result)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean {
|
override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import com.tangem.datasource.local.token.StakingYieldsStore
|
||||||
import com.tangem.domain.staking.repositories.*
|
import com.tangem.domain.staking.repositories.*
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -38,7 +37,6 @@ internal object StakingDataModule {
|
||||||
stakingBalanceStore: StakingBalanceStore,
|
stakingBalanceStore: StakingBalanceStore,
|
||||||
cacheRegistry: CacheRegistry,
|
cacheRegistry: CacheRegistry,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
stakingFeatureToggle: StakingFeatureToggles,
|
|
||||||
walletManagersFacade: WalletManagersFacade,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
getUserWalletUseCase: GetUserWalletUseCase,
|
getUserWalletUseCase: GetUserWalletUseCase,
|
||||||
@NetworkMoshi moshi: Moshi,
|
@NetworkMoshi moshi: Moshi,
|
||||||
|
|
@ -49,7 +47,6 @@ internal object StakingDataModule {
|
||||||
stakingBalanceStore = stakingBalanceStore,
|
stakingBalanceStore = stakingBalanceStore,
|
||||||
cacheRegistry = cacheRegistry,
|
cacheRegistry = cacheRegistry,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
stakingFeatureToggle = stakingFeatureToggle,
|
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
getUserWalletUseCase = getUserWalletUseCase,
|
getUserWalletUseCase = getUserWalletUseCase,
|
||||||
moshi = moshi,
|
moshi = moshi,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ dependencies {
|
||||||
implementation(projects.domain.tokens.models)
|
implementation(projects.domain.tokens.models)
|
||||||
implementation(projects.domain.txhistory.models)
|
implementation(projects.domain.txhistory.models)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
|
implementation(projects.domain.staking.models)
|
||||||
|
|
||||||
/** Project - Data */
|
/** Project - Data */
|
||||||
implementation(projects.core.datasource)
|
implementation(projects.core.datasource)
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ package com.tangem.data.tokens.di
|
||||||
|
|
||||||
import com.tangem.data.common.cache.CacheRegistry
|
import com.tangem.data.common.cache.CacheRegistry
|
||||||
import com.tangem.data.tokens.repository.*
|
import com.tangem.data.tokens.repository.*
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
|
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
|
||||||
import com.tangem.datasource.local.network.NetworksStatusesStore
|
import com.tangem.datasource.local.network.NetworksStatusesStore
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.quote.QuotesStore
|
import com.tangem.datasource.local.quote.QuotesStore
|
||||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.tokens.repository.*
|
import com.tangem.domain.tokens.repository.*
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
|
|
@ -26,23 +25,21 @@ internal object TokensDataModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideCurrenciesRepository(
|
fun provideCurrenciesRepository(
|
||||||
tangemTechApi: TangemTechApi,
|
tangemTechApi: TangemTechApi,
|
||||||
tangemExpressApi: TangemExpressApi,
|
|
||||||
appPreferencesStore: AppPreferencesStore,
|
appPreferencesStore: AppPreferencesStore,
|
||||||
userWalletsStore: UserWalletsStore,
|
userWalletsStore: UserWalletsStore,
|
||||||
walletManagersFacade: WalletManagersFacade,
|
walletManagersFacade: WalletManagersFacade,
|
||||||
expressAssetsStore: ExpressAssetsStore,
|
|
||||||
cacheRegistry: CacheRegistry,
|
cacheRegistry: CacheRegistry,
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
swapServiceLoader: SwapServiceLoader,
|
||||||
): CurrenciesRepository {
|
): CurrenciesRepository {
|
||||||
return DefaultCurrenciesRepository(
|
return DefaultCurrenciesRepository(
|
||||||
tangemTechApi = tangemTechApi,
|
tangemTechApi = tangemTechApi,
|
||||||
tangemExpressApi = tangemExpressApi,
|
|
||||||
appPreferencesStore = appPreferencesStore,
|
|
||||||
walletManagersFacade = walletManagersFacade,
|
|
||||||
userWalletsStore = userWalletsStore,
|
userWalletsStore = userWalletsStore,
|
||||||
expressAssetsStore = expressAssetsStore,
|
walletManagersFacade = walletManagersFacade,
|
||||||
cacheRegistry = cacheRegistry,
|
cacheRegistry = cacheRegistry,
|
||||||
|
appPreferencesStore = appPreferencesStore,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
|
swapServiceLoader = swapServiceLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,15 +81,6 @@ internal object TokensDataModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideDefaultMarketCoinsRepository(
|
|
||||||
expressAssetsStore: ExpressAssetsStore,
|
|
||||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
|
||||||
): MarketCryptoCurrencyRepository {
|
|
||||||
return DefaultMarketCryptoCurrencyRepository(expressAssetsStore, coroutineDispatcherProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideCurrencyChecksRepository(
|
fun provideCurrencyChecksRepository(
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,14 @@ import com.tangem.data.tokens.utils.CustomTokensMerger
|
||||||
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
||||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||||
import com.tangem.datasource.api.common.response.getOrThrow
|
import com.tangem.datasource.api.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.express.TangemExpressApi
|
|
||||||
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
|
|
||||||
import com.tangem.datasource.api.express.models.request.AssetsRequestBody
|
|
||||||
import com.tangem.datasource.api.express.models.request.LeastTokenInfo
|
|
||||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||||
|
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
|
||||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
import com.tangem.datasource.local.preferences.utils.getObject
|
import com.tangem.datasource.local.preferences.utils.getObject
|
||||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
|
||||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||||
import com.tangem.domain.core.error.DataError
|
import com.tangem.domain.core.error.DataError
|
||||||
import com.tangem.domain.demo.DemoConfig
|
import com.tangem.domain.demo.DemoConfig
|
||||||
|
|
@ -46,12 +42,11 @@ import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency
|
||||||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||||
internal class DefaultCurrenciesRepository(
|
internal class DefaultCurrenciesRepository(
|
||||||
private val tangemTechApi: TangemTechApi,
|
private val tangemTechApi: TangemTechApi,
|
||||||
private val tangemExpressApi: TangemExpressApi,
|
|
||||||
private val userWalletsStore: UserWalletsStore,
|
private val userWalletsStore: UserWalletsStore,
|
||||||
private val walletManagersFacade: WalletManagersFacade,
|
private val walletManagersFacade: WalletManagersFacade,
|
||||||
private val expressAssetsStore: ExpressAssetsStore,
|
|
||||||
private val cacheRegistry: CacheRegistry,
|
private val cacheRegistry: CacheRegistry,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
|
private val swapServiceLoader: SwapServiceLoader,
|
||||||
private val dispatchers: CoroutineDispatcherProvider,
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
) : CurrenciesRepository {
|
) : CurrenciesRepository {
|
||||||
|
|
||||||
|
|
@ -298,23 +293,28 @@ internal class DefaultCurrenciesRepository(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
id: CryptoCurrency.ID,
|
id: CryptoCurrency.ID,
|
||||||
): CryptoCurrency = withContext(dispatchers.io) {
|
): CryptoCurrency = withContext(dispatchers.io) {
|
||||||
val userWallet = getUserWallet(userWalletId)
|
getMultiCurrencyWalletCurrency(userWalletId, id.value)
|
||||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
|
|
||||||
|
|
||||||
val response = requireNotNull(
|
|
||||||
value = getSavedUserTokensResponseSync(key = userWalletId),
|
|
||||||
lazyMessage = {
|
|
||||||
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
responseCurrenciesFactory.createCurrency(
|
|
||||||
currencyId = id,
|
|
||||||
response = response,
|
|
||||||
scanResponse = userWallet.scanResponse,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency =
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
val userWallet = getUserWallet(userWalletId)
|
||||||
|
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true)
|
||||||
|
|
||||||
|
val response = requireNotNull(
|
||||||
|
value = getSavedUserTokensResponseSync(key = userWalletId),
|
||||||
|
lazyMessage = {
|
||||||
|
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
responseCurrenciesFactory.createCurrency(
|
||||||
|
currencyId = id,
|
||||||
|
response = response,
|
||||||
|
scanResponse = userWallet.scanResponse,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getNetworkCoin(
|
override suspend fun getNetworkCoin(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
networkId: Network.ID,
|
networkId: Network.ID,
|
||||||
|
|
@ -565,27 +565,7 @@ internal class DefaultCurrenciesRepository(
|
||||||
userWalletId: UserWalletId,
|
userWalletId: UserWalletId,
|
||||||
userTokens: UserTokensResponse,
|
userTokens: UserTokensResponse,
|
||||||
) {
|
) {
|
||||||
try {
|
swapServiceLoader.update(userWalletId, userTokens)
|
||||||
val tokensList = userTokens.tokens
|
|
||||||
.map {
|
|
||||||
LeastTokenInfo(
|
|
||||||
contractAddress = it.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE,
|
|
||||||
network = it.networkId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokensList.isNotEmpty()) {
|
|
||||||
val response = tangemExpressApi.getAssets(
|
|
||||||
AssetsRequestBody(
|
|
||||||
tokensList = tokensList,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
expressAssetsStore.store(userWalletId, response.getOrThrow())
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Timber.e(e, "Unable to fetch assets for: ${userWalletId.stringValue}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleFetchTokensError(userWallet: UserWallet, e: ApiResponseError): UserTokensResponse {
|
private suspend fun handleFetchTokensError(userWallet: UserWallet, e: ApiResponseError): UserTokensResponse {
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,17 @@ import com.tangem.blockchain.common.MinimumSendAmountProvider
|
||||||
import com.tangem.blockchain.common.ReserveAmountProvider
|
import com.tangem.blockchain.common.ReserveAmountProvider
|
||||||
import com.tangem.blockchain.common.UtxoAmountLimitProvider
|
import com.tangem.blockchain.common.UtxoAmountLimitProvider
|
||||||
import com.tangem.data.tokens.converters.UtxoConverter
|
import com.tangem.data.tokens.converters.UtxoConverter
|
||||||
|
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||||
|
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||||
import com.tangem.domain.tokens.model.CurrencyAmount
|
import com.tangem.domain.tokens.model.CurrencyAmount
|
||||||
import com.tangem.domain.tokens.model.Network
|
import com.tangem.domain.tokens.model.Network
|
||||||
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
||||||
|
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.domain.wallets.models.UserWalletId
|
import com.tangem.domain.wallets.models.UserWalletId
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import com.tangem.utils.extensions.isZero
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
|
@ -118,4 +122,23 @@ internal class DefaultCurrencyChecksRepository(
|
||||||
}
|
}
|
||||||
return utxoAmount?.let(UtxoConverter()::convert)
|
return utxoAmount?.let(UtxoConverter()::convert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getRentInfoWarning(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
currencyStatus: CryptoCurrencyStatus,
|
||||||
|
balanceAfterTransaction: BigDecimal?,
|
||||||
|
): CryptoCurrencyWarning.Rent? {
|
||||||
|
val rentData = walletManagersFacade.getRentInfo(userWalletId, currencyStatus.currency.network) ?: return null
|
||||||
|
val balanceValue = currencyStatus.value as? CryptoCurrencyStatus.Loaded ?: return null
|
||||||
|
val stakingTotalBalance =
|
||||||
|
(balanceValue.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance() ?: BigDecimal.ZERO
|
||||||
|
val amount = balanceAfterTransaction ?: balanceValue.amount
|
||||||
|
return when {
|
||||||
|
amount.isZero() && stakingTotalBalance.isZero() -> null
|
||||||
|
amount < rentData.exemptionAmount && stakingTotalBalance.isZero() -> {
|
||||||
|
CryptoCurrencyWarning.Rent(rentData.rent, rentData.exemptionAmount)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue