Updated on 2026-08-14
This commit is contained in:
commit
00805c1380
268 changed files with 6515 additions and 1380 deletions
|
|
@ -74,6 +74,7 @@ dependencies {
|
|||
implementation(projects.domain.walletConnect)
|
||||
implementation(projects.domain.markets)
|
||||
implementation(projects.domain.manageTokens)
|
||||
implementation(projects.domain.onramp)
|
||||
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -112,6 +113,7 @@ dependencies {
|
|||
implementation(projects.data.walletConnect)
|
||||
implementation(projects.data.markets)
|
||||
implementation(projects.data.manageTokens)
|
||||
implementation(projects.data.onramp)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.onboarding)
|
||||
|
|
|
|||
|
|
@ -749,6 +749,16 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "chiliz",
|
||||
"name": "Chiliz",
|
||||
"symbol": "CHZ",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "chiliz/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "casper-network",
|
||||
"name": "Casper",
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
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.utils.PUSH_PERMISSION
|
||||
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.wallet.navigation.WalletRouter
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.api.BackupServiceHolder
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.sdk.extensions.init
|
||||
import com.tangem.tap.common.ActivityResultCallbackHolder
|
||||
|
|
@ -185,6 +187,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
@Inject
|
||||
lateinit var shouldInitiallyAskPermissionUseCase: ShouldInitiallyAskPermissionUseCase
|
||||
|
||||
@Inject
|
||||
lateinit var backupServiceHolder: BackupServiceHolder
|
||||
|
||||
@Inject
|
||||
lateinit var onboardingV2FeatureToggles: OnboardingV2FeatureToggles
|
||||
|
||||
internal val viewModel: MainViewModel by viewModels()
|
||||
|
||||
private lateinit var appThemeModeFlow: SharedFlow<AppThemeMode>
|
||||
|
|
@ -294,7 +302,14 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
cardSdkOwner.register(activity = this)
|
||||
tangemSdkManager = injectedTangemSdkManager
|
||||
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(
|
||||
owner = this,
|
||||
settingsRepository = settingsRepository,
|
||||
|
|
@ -550,6 +565,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
}
|
||||
|
||||
// TODO add onboarding v2 handling
|
||||
store.dispatch(BackupAction.CheckForUnfinishedBackup)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,12 +64,16 @@ private fun handleAction(action: Action, appState: () -> AppState?) {
|
|||
}
|
||||
val cardProvider: () -> CardDTO? = { scanResponseProvider.invoke()?.card }
|
||||
|
||||
val buyService = makeBuyExchangeService(config)
|
||||
val sellService = makeSellExchangeService(config)
|
||||
val exchangeManager = CurrencyExchangeManager(
|
||||
buyService = makeBuyExchangeService(config),
|
||||
sellService = makeSellExchangeService(config),
|
||||
buyService = buyService,
|
||||
sellService = sellService,
|
||||
primaryRules = CardExchangeRules(cardProvider),
|
||||
)
|
||||
// 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.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager))
|
||||
store.dispatchOnMain(GlobalAction.ExchangeManager.Update)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
|
||||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
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.network.exchangeServices.DefaultRampManager
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -39,8 +42,18 @@ internal object ActivityModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDefaultRampManager(appStateHolder: AppStateHolder): RampStateManager {
|
||||
return DefaultRampManager(appStateHolder.exchangeService)
|
||||
fun provideDefaultRampManager(
|
||||
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
|
||||
|
|
|
|||
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()
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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.walletmanager.WalletManagersFacade
|
||||
import com.tangem.feature.swap.domain.api.SwapRepository
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -119,26 +118,26 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
swapRepository: SwapRepository,
|
||||
currencyChecksRepository: CurrencyChecksRepository,
|
||||
showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
|
||||
promoRepository: PromoRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
rampStateManager: RampStateManager,
|
||||
): GetCurrencyWarningsUseCase {
|
||||
return GetCurrencyWarningsUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
|
||||
currencyChecksRepository = currencyChecksRepository,
|
||||
swapRepository = swapRepository,
|
||||
showSwapPromoTokenUseCase = showSwapPromoTokenUseCase,
|
||||
promoRepository = promoRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
dispatchers = dispatchers,
|
||||
rampStateManager = rampStateManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -216,23 +215,19 @@ internal object TokensDomainModule {
|
|||
fun provideGetCryptoCurrencyActionsUseCase(
|
||||
rampStateManager: RampStateManager,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCryptoCurrencyActionsUseCase {
|
||||
return GetCryptoCurrencyActionsUseCase(
|
||||
rampManager = rampStateManager,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
marketCryptoCurrencyRepository = marketCryptoCurrencyRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
stakingFeatureToggles = stakingFeatureToggles,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
|||
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
|
||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||
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.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.features.main.model.MainScreenState
|
||||
|
|
@ -40,7 +39,6 @@ internal class MainViewModel @Inject constructor(
|
|||
private val blockchainSDKFactory: BlockchainSDKFactory,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
||||
private val apiConfigsManager: ApiConfigsManager,
|
||||
homeFeatureToggles: HomeFeatureToggles,
|
||||
|
|
@ -78,9 +76,7 @@ internal class MainViewModel @Inject constructor(
|
|||
displayBalancesHidingStatusToast()
|
||||
displayHiddenBalancesModalNotification()
|
||||
|
||||
if (stakingFeatureToggles.isStakingEnabled) {
|
||||
fetchStakingTokens()
|
||||
}
|
||||
fetchStakingTokens()
|
||||
|
||||
deleteDeprecatedLogsUseCase()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
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.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
|
|
@ -67,7 +68,10 @@ object OnboardingHelper {
|
|||
|
||||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||
if (store.inject(DaggerGraphState::onboardingV2FeatureToggles).isOnboardingV2Enabled) {
|
||||
return AppRoute.Onboarding(scanResponse)
|
||||
return AppRoute.Onboarding(
|
||||
scanResponse = scanResponse,
|
||||
startFromBackup = false,
|
||||
)
|
||||
}
|
||||
|
||||
return when (val type = scanResponse.productType) {
|
||||
|
|
@ -88,6 +92,7 @@ object OnboardingHelper {
|
|||
}
|
||||
|
||||
fun saveWallet(
|
||||
alreadyCreatedWallet: UserWallet?,
|
||||
scanResponse: ScanResponse,
|
||||
accessCode: 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,
|
||||
// then open save wallet screen
|
||||
tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
|
||||
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
|
||||
else -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError, alreadyCreatedWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -242,9 +247,10 @@ object OnboardingHelper {
|
|||
scanResponse: ScanResponse,
|
||||
backupCardsIds: List<String>?,
|
||||
hasBackupError: Boolean,
|
||||
alreadyCreatedWallet: UserWallet? = null,
|
||||
) {
|
||||
val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase)
|
||||
val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||
val userWallet = alreadyCreatedWallet ?: UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||
.hasBackupError(hasBackupError)
|
||||
.backupCardsIds(backupCardsIds?.toSet())
|
||||
.build()
|
||||
|
|
|
|||
|
|
@ -196,11 +196,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 updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
|
||||
|
||||
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
val userWalletId = userWallet?.walletId ?: UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
if (backupState.hasRing && userWalletId != null) {
|
||||
scope.launch {
|
||||
store.inject(DaggerGraphState::walletsRepository).setHasWalletsWithRing(userWalletId = userWalletId)
|
||||
|
|
@ -208,6 +208,7 @@ private fun handleFinishBackup(scanResponse: ScanResponse) {
|
|||
}
|
||||
|
||||
OnboardingHelper.saveWallet(
|
||||
alreadyCreatedWallet = userWallet,
|
||||
scanResponse = updatedScanResponse,
|
||||
accessCode = backupState.accessCode,
|
||||
backupCardsIds = backupState.backupCardIds,
|
||||
|
|
@ -667,7 +668,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
Analytics.send(Onboarding.Finished())
|
||||
|
||||
store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds)
|
||||
handleFinishBackup(requireNotNull(scanResponse))
|
||||
handleFinishBackup(requireNotNull(scanResponse), userWallet)
|
||||
delay(1000)
|
||||
store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet?.walletId))
|
||||
}
|
||||
|
|
@ -728,6 +729,7 @@ private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: Ba
|
|||
return requireNotNull(
|
||||
value = UserWalletBuilder(scanResponse, walletNameGenerateUseCase)
|
||||
.backupCardsIds(backupState.backupCardIds.toSet())
|
||||
.hasBackupError(backupState.hasBackupError)
|
||||
.build(),
|
||||
lazyMessage = { "User wallet not created" },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,11 +55,15 @@ object TradeCryptoMiddleware {
|
|||
|
||||
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
||||
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() {
|
||||
store.dispatchNavigationAction { push(AppRoute.Onramp) }
|
||||
private fun proceedWithOnramp(cryptoCurrency: CryptoCurrency) {
|
||||
store.dispatchNavigationAction { push(AppRoute.Onramp(cryptoCurrency)) }
|
||||
}
|
||||
|
||||
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.extensions.Result
|
||||
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.ScanResponse
|
||||
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.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
@ -30,9 +34,19 @@ class CurrencyExchangeManager(
|
|||
private val primaryRules: ExchangeRules,
|
||||
) : ExchangeService {
|
||||
|
||||
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
get() = _initializationStatus
|
||||
|
||||
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
|
||||
MutableStateFlow(value = lceLoading())
|
||||
|
||||
override suspend fun update() {
|
||||
_initializationStatus.value = lceLoading()
|
||||
|
||||
buyService.update()
|
||||
sellService.update()
|
||||
|
||||
_initializationStatus.value = lceContent()
|
||||
}
|
||||
|
||||
override fun isBuyAllowed(): Boolean = primaryRules.isBuyAllowed() && buyService.isBuyAllowed()
|
||||
|
|
|
|||
|
|
@ -1,15 +1,29 @@
|
|||
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.models.scan.ScanResponse
|
||||
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()
|
||||
|
||||
override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean {
|
||||
return exchangeService?.availableForBuy(
|
||||
scanResponse,
|
||||
scanResponse = scanResponse,
|
||||
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
||||
) ?: false
|
||||
}
|
||||
|
|
@ -19,4 +33,46 @@ class DefaultRampManager(private val exchangeService: ExchangeService?) : RampSt
|
|||
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
|
||||
) ?: 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
|
||||
|
||||
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.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
typealias ExchangeServiceInitializationStatus = Lce<Throwable, Any>
|
||||
|
||||
interface Exchanger {
|
||||
fun isBuyAllowed(): Boolean
|
||||
|
|
@ -12,10 +18,17 @@ interface Exchanger {
|
|||
}
|
||||
|
||||
interface ExchangeService : Exchanger, ExchangeUrlBuilder {
|
||||
|
||||
val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
|
||||
suspend fun update()
|
||||
|
||||
companion object {
|
||||
fun dummy(): ExchangeService = object : ExchangeService {
|
||||
|
||||
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus> =
|
||||
MutableStateFlow(value = lceLoading())
|
||||
|
||||
override suspend fun update() {}
|
||||
override fun isBuyAllowed(): Boolean = false
|
||||
override fun isSellAllowed(): Boolean = false
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ internal val Blockchain.mercuryoNetwork: String?
|
|||
Blockchain.CasperTestnet -> null
|
||||
Blockchain.Core -> null
|
||||
Blockchain.CoreTestnet -> null
|
||||
Blockchain.Chiliz -> null
|
||||
Blockchain.ChilizTestnet -> null
|
||||
Blockchain.Unknown -> null
|
||||
Blockchain.Xodex -> null
|
||||
Blockchain.Canxium -> null
|
||||
|
|
|
|||
|
|
@ -6,12 +6,19 @@ import com.tangem.common.extensions.calculateSha512
|
|||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.services.Result
|
||||
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.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
||||
import com.tangem.tap.network.exchangeServices.ExchangeServiceInitializationStatus
|
||||
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
|
||||
|
||||
/**
|
||||
|
|
@ -19,10 +26,34 @@ import java.util.concurrent.CopyOnWriteArrayList
|
|||
*/
|
||||
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 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 isSellAllowed(): Boolean = false
|
||||
|
|
@ -42,18 +73,6 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
|
|||
|
||||
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(
|
||||
action: CurrencyExchangeManager.Action,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
|
|
@ -89,6 +108,9 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
|
|||
private fun handleSuccessfullyUpdatedData(data: MercuryoCurrenciesResponse.Data) {
|
||||
availableMercuryoCurrencies.clear()
|
||||
availableMercuryoCurrencies.addAll(data.config.cryptoCurrencies)
|
||||
|
||||
Timber.i("Successfully updated")
|
||||
_initializationStatus.value = lceContent()
|
||||
}
|
||||
|
||||
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.datasource.api.common.createRetrofitInstance
|
||||
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.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.domain.model.Currency
|
||||
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
|
||||
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.moonpay.models.MoonPayAvailableCurrency
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import timber.log.Timber
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
|
@ -23,6 +30,12 @@ class MoonPayService(
|
|||
private val logEnabled: Boolean,
|
||||
) : ExchangeService {
|
||||
|
||||
override val initializationStatus: StateFlow<ExchangeServiceInitializationStatus>
|
||||
get() = _initializationStatus
|
||||
|
||||
private val _initializationStatus: MutableStateFlow<ExchangeServiceInitializationStatus> =
|
||||
MutableStateFlow(value = lceLoading())
|
||||
|
||||
private val api: MoonPayApi by lazy {
|
||||
createRetrofitInstance(
|
||||
baseUrl = MoonPayApi.MOOONPAY_BASE_URL,
|
||||
|
|
@ -34,14 +47,25 @@ class MoonPayService(
|
|||
|
||||
override suspend fun update() {
|
||||
withIOContext {
|
||||
Timber.i("Start updating")
|
||||
_initializationStatus.value = lceLoading()
|
||||
|
||||
performRequest {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +82,8 @@ class MoonPayService(
|
|||
)
|
||||
}
|
||||
|
||||
Timber.i("Successfully updated")
|
||||
_initializationStatus.value = lceContent()
|
||||
status = MoonPayStatus(currenciesToSell, userStatus, currencies)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
|||
Filecoin -> MoonPaySupportedCurrency(networkCode = "filecoin", currencyCode = "fil")
|
||||
Sui -> MoonPaySupportedCurrency(networkCode = "sui", currencyCode = "sui")
|
||||
Core -> MoonPaySupportedCurrency(networkCode = "core", currencyCode = "core")
|
||||
Chiliz -> MoonPaySupportedCurrency(networkCode = "ethereum", currencyCode = "chz")
|
||||
ArbitrumTestnet -> null
|
||||
AvalancheTestnet -> null
|
||||
BinanceTestnet -> null
|
||||
|
|
@ -139,6 +140,7 @@ internal val Blockchain.moonPaySupportedCurrency: MoonPaySupportedCurrency?
|
|||
Casper -> null
|
||||
CasperTestnet -> null
|
||||
CoreTestnet -> null
|
||||
ChilizTestnet -> null
|
||||
Unknown -> null
|
||||
Xodex -> null
|
||||
Canxium -> null
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.proxy
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
|
|
@ -26,10 +25,8 @@ class AppStateHolder @Inject constructor() : ReduxStateHolder {
|
|||
var mainStore: Store<AppState>? = null
|
||||
var tangemSdkManager: TangemSdkManager? = null
|
||||
var exchangeService: ExchangeService? = null
|
||||
|
||||
fun getActualCard(): CardDTO? {
|
||||
return scanResponse?.card
|
||||
}
|
||||
var buyService: ExchangeService? = null
|
||||
var sellService: ExchangeService? = null
|
||||
|
||||
override fun dispatch(action: Action) {
|
||||
mainStore?.dispatch(action)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
|
||||
override val stack: Value<ChildStack<AppRoute, Child>> = childStack(
|
||||
source = navigationProvider.getOrCreateTyped(),
|
||||
serializer = AppRoute.serializer(),
|
||||
serializer = AppRoute.serializer(), // TODO Maybe set this to null for AppRoute.Onboarding case. Need to check
|
||||
initialConfiguration = getInitialRoute(),
|
||||
handleBackButton = false,
|
||||
childFactory = ::child,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
|||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
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.onramp.component.BuyCryptoComponent
|
||||
import com.tangem.features.onramp.component.OnrampComponent
|
||||
import com.tangem.features.onramp.component.SellCryptoComponent
|
||||
import com.tangem.features.onramp.component.SwapSelectTokensComponent
|
||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.features.staking.api.navigation.StakingRouter
|
||||
|
|
@ -53,6 +54,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val onrampComponentFactory: OnrampComponent.Factory,
|
||||
private val buyCryptoComponentFactory: BuyCryptoComponent.Factory,
|
||||
private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
|
||||
private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory,
|
||||
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
|
||||
private val sendRouter: SendRouter,
|
||||
private val tokenDetailsRouter: TokenDetailsRouter,
|
||||
|
|
@ -197,7 +199,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.Onramp -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = OnrampComponent.Params(),
|
||||
params = OnrampComponent.Params(route.currency.name),
|
||||
componentFactory = onrampComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
@ -215,10 +217,20 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = sellCryptoComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.SwapCrypto -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = SwapSelectTokensComponent.Params(userWalletId = route.userWalletId),
|
||||
componentFactory = swapSelectTokensComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.Onboarding -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = OnboardingEntryComponent.Params(route.scanResponse),
|
||||
params = OnboardingEntryComponent.Params(
|
||||
scanResponse = route.scanResponse,
|
||||
startBackupFlow = route.startFromBackup,
|
||||
),
|
||||
componentFactory = onboardingEntryComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,16 +219,24 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data class Swap(
|
||||
val currency: CryptoCurrency,
|
||||
val currencyFrom: CryptoCurrency,
|
||||
val currencyTo: CryptoCurrency? = null,
|
||||
val userWalletId: UserWalletId,
|
||||
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 {
|
||||
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
|
||||
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 IS_INITIAL_REVERSE_ORDER = "isInitialReverseOrder"
|
||||
}
|
||||
|
|
@ -287,7 +295,9 @@ sealed class AppRoute(val path: String) : Route {
|
|||
}
|
||||
|
||||
@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 BuyCrypto(
|
||||
|
|
@ -299,6 +309,14 @@ sealed class AppRoute(val path: String) : Route {
|
|||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/sell_crypto/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class SwapCrypto(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/swap_crypto/${userWalletId.stringValue}")
|
||||
|
||||
// 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 ""}")
|
||||
}
|
||||
|
|
@ -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.utils.PriceChangeConverter
|
||||
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.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
|
|
@ -39,8 +40,8 @@ class TokenItemStateConverter(
|
|||
private val fiatAmountStateProvider: (CryptoCurrencyStatus) -> TokenItemState.FiatAmountState? = {
|
||||
createFiatAmountState(it, appCurrency)
|
||||
},
|
||||
private val onItemClick: (CryptoCurrencyStatus) -> Unit,
|
||||
private val onItemLongClick: ((CryptoCurrencyStatus) -> Unit)? = null,
|
||||
private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
private val onItemLongClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
||||
|
|
@ -75,9 +76,11 @@ class TokenItemStateConverter(
|
|||
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||
fiatAmountState = requireNotNull(fiatAmountStateProvider(this)),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = getFormattedAmount()),
|
||||
onItemClick = { onItemClick(this) },
|
||||
onItemLongClick = onItemLongClick?.let {
|
||||
{ it(this) }
|
||||
onItemClick = onItemClick?.let { onItemClick ->
|
||||
{ onItemClick(it, this) }
|
||||
},
|
||||
onItemLongClick = onItemLongClick?.let { onItemLongClick ->
|
||||
{ onItemLongClick(it, this) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -94,9 +97,11 @@ class TokenItemStateConverter(
|
|||
iconState = iconStateProvider(this),
|
||||
titleState = titleStateProvider(this),
|
||||
subtitleState = subtitleStateProvider(this),
|
||||
onItemClick = { onItemClick(this) },
|
||||
onItemLongClick = onItemLongClick?.let {
|
||||
{ it(this) }
|
||||
onItemClick = onItemClick?.let { onItemClick ->
|
||||
{ onItemClick(it, this) }
|
||||
},
|
||||
onItemLongClick = onItemLongClick?.let { onItemLongClick ->
|
||||
{ onItemLongClick(it, this) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -107,8 +112,8 @@ class TokenItemStateConverter(
|
|||
iconState = iconStateProvider(this),
|
||||
titleState = titleStateProvider(this),
|
||||
subtitleState = subtitleStateProvider(this),
|
||||
onItemLongClick = onItemLongClick?.let {
|
||||
{ it(this) }
|
||||
onItemLongClick = onItemLongClick?.let { onItemLongClick ->
|
||||
{ onItemLongClick(it, this) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -122,7 +127,7 @@ class TokenItemStateConverter(
|
|||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> {
|
||||
TokenItemState.TitleState.Content(text = currencyStatus.currency.name)
|
||||
TokenItemState.TitleState.Content(text = stringReference(currencyStatus.currency.name))
|
||||
}
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
|
|
@ -130,7 +135,7 @@ class TokenItemStateConverter(
|
|||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> {
|
||||
TokenItemState.TitleState.Content(
|
||||
text = currencyStatus.currency.name,
|
||||
text = stringReference(currencyStatus.currency.name),
|
||||
hasPending = value.hasCurrentNetworkTransactions,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ dependencies {
|
|||
implementation(projects.core.utils)
|
||||
implementation(projects.libs.auth)
|
||||
implementation(projects.domain.appTheme.models)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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.express.TangemExpressApi
|
||||
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.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
|
|
@ -99,24 +98,22 @@ internal object NetworkModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampApi(
|
||||
// @NetworkMoshi moshi: Moshi,
|
||||
// @ApplicationContext context: Context,
|
||||
// apiConfigsManager: ApiConfigsManager,
|
||||
// appLogsStore: AppLogsStore,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
apiConfigsManager: ApiConfigsManager,
|
||||
appLogsStore: AppLogsStore,
|
||||
): OnrampApi {
|
||||
// TODO: Remove when backend will be ready - [REDACTED_TASK_KEY]
|
||||
return MockedOnrampApi()
|
||||
// return createApi(
|
||||
// id = ApiConfig.ID.Express,
|
||||
// moshi = moshi,
|
||||
// context = context,
|
||||
// apiConfigsManager = apiConfigsManager,
|
||||
// clientBuilder = {
|
||||
// addInterceptor(
|
||||
// NetworkLogsSaveInterceptor(appLogsStore),
|
||||
// )
|
||||
// },
|
||||
// )
|
||||
return createApi(
|
||||
id = ApiConfig.ID.Express,
|
||||
moshi = moshi,
|
||||
context = context,
|
||||
apiConfigsManager = apiConfigsManager,
|
||||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -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>>>
|
||||
}
|
||||
|
|
@ -103,6 +103,10 @@ object PreferencesKeys {
|
|||
|
||||
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") }
|
||||
|
||||
// region Permission
|
||||
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,6 @@
|
|||
"name": "WC_SOLANA_TX_SIGN_ENABLED",
|
||||
"version": "5.18.0"
|
||||
},
|
||||
{
|
||||
"name": "STAKING_ENABLED",
|
||||
"version": "5.15.0"
|
||||
},
|
||||
{
|
||||
"name": "IS_ETHEREUM_EIP_1559_ENABLED",
|
||||
"version": "5.17.0"
|
||||
|
|
@ -25,7 +21,7 @@
|
|||
},
|
||||
{
|
||||
"name": "MAIN_ACTION_BUTTONS_ENABLED",
|
||||
"version": "undefined"
|
||||
"version": "5.19.0"
|
||||
},
|
||||
{
|
||||
"name": "ONBOARDING_CODE_REFACTORING_ENABLED",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<string name="action_buttons_swap_empty_search_message">Token nicht in Deinem Portfolio gefunden? Überprüfe die Märkte, um es für Swaps zu finden und hinzuzufügen</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">Es gibt keine verfügbaren Token, die mit dem ausgewählten Token getauscht werden können. Bitte wähle einen anderen.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_title">Kein verfügbares Paar</string>
|
||||
<string name="action_buttons_you_want_to_receive">Du möchtest erhalten</string>
|
||||
<string name="action_buttons_you_want_to_swap">Du möchtest tauschen</string>
|
||||
<string name="add_custom_token_choose_network">Netzwerk wählen</string>
|
||||
<string name="add_custom_token_title">Token anlegen</string>
|
||||
<string name="add_tokens_title">Token verwalten</string>
|
||||
|
|
@ -260,7 +262,7 @@
|
|||
<string name="express_exchange_status_canceled">Abgebrochen</string>
|
||||
<string name="express_exchange_status_confirmed">Bestätigt</string>
|
||||
<string name="express_exchange_status_confirming">Bestätigen</string>
|
||||
<string name="express_exchange_status_confirming_active">wird bestätigt...</string>
|
||||
<string name="express_exchange_status_confirming_active">Wird bestätigt...</string>
|
||||
<string name="express_exchange_status_exchanged">Getauscht</string>
|
||||
<string name="express_exchange_status_exchanging">Tauschen läuft</string>
|
||||
<string name="express_exchange_status_exchanging_active">Austauschen...</string>
|
||||
|
|
@ -451,6 +453,7 @@
|
|||
<string name="markets_token_details_price_performance">Preisleistung</string>
|
||||
<string name="markets_token_details_repository">Aufbewahrungsort</string>
|
||||
<string name="markets_token_details_security_score">Sicherheitsbewertung</string>
|
||||
<string name="markets_token_details_security_score_description">Der Security Score eines Tokens ist eine Metrik, die das Sicherheitsniveau einer Blockchain oder eines Tokens anhand verschiedener Faktoren bewertet und aus den unten aufgeführten Quellen zusammengestellt wird.</string>
|
||||
<string name="markets_token_details_social">Soziales</string>
|
||||
<string name="markets_token_details_total_supply">Gesamtangebot</string>
|
||||
<string name="markets_token_details_total_supply_description">Die maximale Anzahl von Coins oder Tokens, die jemals für eine bestimmte Kryptowährung existieren können</string>
|
||||
|
|
@ -675,7 +678,7 @@
|
|||
<string name="send_notification_high_fee_text">Aufgrund der Besonderheiten des Netzes %1$s ist die Gebühr für die Überweisung des gesamten Guthabens höher. Um die Kommission zu reduzieren, Kannst du %2$s verlassen.</string>
|
||||
<string name="send_notification_high_fee_title">Die Gebühr ist höher</string>
|
||||
<string name="send_notification_invalid_amount_text">Die enthaltene Kommission übersteigt den Überweisungsbetrag, was zu einem negativen Wert führt</string>
|
||||
<string name="send_notification_invalid_amount_title">ungültige Menge</string>
|
||||
<string name="send_notification_invalid_amount_title">Ungültige Menge</string>
|
||||
<string name="send_notification_invalid_minimum_amount_text">Der Mindestbetrag für den Versand beträgt %1$s. Bitte stell sicher, dass der Restbetrag nach dem Versand nicht unter %2$s liegt.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_text">Das Zielkonto wurde nicht erstellt. Bitte änder den zu sendenden Betrag.</string>
|
||||
<string name="send_notification_invalid_reserve_amount_title">Der zu sendende Betrag muss mindestens %s betragen</string>
|
||||
|
|
@ -804,7 +807,7 @@
|
|||
<string name="staking_unbonding">Lösen der Bindungen</string>
|
||||
<string name="staking_unlocked_locked">Gelocktes unlocken</string>
|
||||
<string name="staking_unlocking">Entsperren</string>
|
||||
<string name="staking_unstake_amount_requirement_error">Der unstaking-Betrag muss mindestens %s betragen.</string>
|
||||
<string name="staking_unstake_amount_requirement_error">Der unstaking-Betrag muss mindestens %s betragen</string>
|
||||
<string name="staking_unstake_amount_validation_error">Der Betrag übersteigt das eingesetzte Guthaben</string>
|
||||
<string name="staking_unstaked">Unstaken</string>
|
||||
<string name="staking_unstaking">Staking beenden</string>
|
||||
|
|
@ -884,6 +887,7 @@
|
|||
<string name="transaction_history_transaction_from_address">von: %s</string>
|
||||
<string name="transaction_history_transaction_to_address">zu: %s</string>
|
||||
<string name="transaction_history_transaction_validator">Validierer: %s</string>
|
||||
<string name="transfer_min_amount_error">Minimum %s</string>
|
||||
<string name="transfer_notification_invalid_minimum_transaction_amount_text">Der Mindesttransaktionsbetrag beträgt %1$s.</string>
|
||||
<string name="try_to_load_data_again_button_title">Versuche es erneut</string>
|
||||
<string name="twin_error_same_card">Du hast dieselbe Karte oder Ring gescannt. Um ein Zwillings-Wallet zu erstellen, musst du die Karte oder Ring mit der Nummer %d scannen.</string>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<string name="action_buttons_swap_empty_search_message">¿No encuentras el token en tu billetera? Consulte los mercados para encontrarlo y agregarlo al intercambio</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">No hay tokens disponibles para intercambiar con el token seleccionado. Por favor elige otro.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_title">No hay par disponible</string>
|
||||
<string name="action_buttons_you_want_to_receive">Desea recibir</string>
|
||||
<string name="action_buttons_you_want_to_swap">Quiere intercambiar</string>
|
||||
<string name="add_custom_token_choose_network">Elige red</string>
|
||||
<string name="add_custom_token_title">Agregue un token personalizado</string>
|
||||
<string name="add_tokens_title">Gestionar tokens</string>
|
||||
|
|
@ -740,7 +742,7 @@
|
|||
<string name="staking_details_warmup_period">Periodo de calentamiento</string>
|
||||
<string name="staking_details_warmup_period_info">El tiempo permitido para activar la participación en la apuesta.</string>
|
||||
<string name="staking_give_permission_fee_footer">La red cobrará una tarifa de aprobación de token para verificar que usted está autorizando el uso de su token para el staking.</string>
|
||||
<string name="staking_legal">Al utilizar la función de staking, usted acepta %1$s y %2$s del proveedor</string>
|
||||
<string name="staking_legal">Al utilizar la función de staking, usted acepta %1$s y %2$s del proveedor</string>
|
||||
<string name="staking_locked">Bloqueado</string>
|
||||
<string name="staking_migrate">Migrar</string>
|
||||
<string name="staking_native">Native staking</string>
|
||||
|
|
@ -802,7 +804,7 @@
|
|||
<string name="staking_unbonding">Desunión</string>
|
||||
<string name="staking_unlocked_locked">Desbloquear</string>
|
||||
<string name="staking_unlocking">Desbloqueando</string>
|
||||
<string name="staking_unstake_amount_requirement_error">El monto del staking debe ser al menos %s</string>
|
||||
<string name="staking_unstake_amount_requirement_error">La cantidad para el staking debe ser al menos %s</string>
|
||||
<string name="staking_unstake_amount_validation_error">El monto excede el saldo apostado</string>
|
||||
<string name="staking_unstaked">Sin staking</string>
|
||||
<string name="staking_unstaking">Unstaking</string>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<string name="action_buttons_swap_empty_search_message">Jeton non trouvé dans votre portefeuille ? Consultez les marchés pour le trouver et l\'ajouter à l\'échange</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">Il n\'y a pas de token disponible pour échanger avec le token sélectionné. Veuillez en choisir un autre.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_title">Aucune paire disponible</string>
|
||||
<string name="action_buttons_you_want_to_receive">Vous souhaitez recevoir</string>
|
||||
<string name="action_buttons_you_want_to_swap">Vous souhaitez échanger</string>
|
||||
<string name="add_custom_token_choose_network">Choisissez le réseau</string>
|
||||
<string name="add_custom_token_title">Ajouter un jeton personnalisé</string>
|
||||
<string name="add_tokens_title">Gérer les jetons</string>
|
||||
|
|
@ -740,7 +742,7 @@
|
|||
<string name="staking_details_warmup_period">Période d\'échauffement</string>
|
||||
<string name="staking_details_warmup_period_info">Le temps imparti pour activer la participation au staking.</string>
|
||||
<string name="staking_give_permission_fee_footer">Le réseau facturera des frais d’approbation de jeton pour vérifier que vous autorisez l’utilisation de votre jeton pour le jalonnement.</string>
|
||||
<string name="staking_legal">En utilisant la fonctionnalité de staking, vous acceptez les %1$s et %2$s du fournisseur</string>
|
||||
<string name="staking_legal">En utilisant la fonctionnalité de staking, vous acceptez les %1$s et %2$s du fournisseur</string>
|
||||
<string name="staking_locked">Bloqué</string>
|
||||
<string name="staking_migrate">Migrer</string>
|
||||
<string name="staking_native">Native staking</string>
|
||||
|
|
@ -802,7 +804,7 @@
|
|||
<string name="staking_unbonding">Dissociation</string>
|
||||
<string name="staking_unlocked_locked">Débloquer</string>
|
||||
<string name="staking_unlocking">Déverrouillage</string>
|
||||
<string name="staking_unstake_amount_requirement_error">Le montant à staker doit être au moins %s</string>
|
||||
<string name="staking_unstake_amount_requirement_error">Le montant à destaker doit être au moins %s</string>
|
||||
<string name="staking_unstake_amount_validation_error">Le montant dépasse le solde misé</string>
|
||||
<string name="staking_unstaked">Non-staké</string>
|
||||
<string name="staking_unstaking">Unstaking</string>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<string name="action_buttons_swap_empty_search_message">ポートフォリオにトークンが見つかりませんか?マーケットから見つけ、スワップのために追加してください</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">選択したトークンと交換できるトークンがありません。別のトークンを選択してください。</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_title">利用可能なペアがありません</string>
|
||||
<string name="action_buttons_you_want_to_receive">受け取りたい</string>
|
||||
<string name="action_buttons_you_want_to_swap">交換したい</string>
|
||||
<string name="add_custom_token_choose_network">ネットワークを選択</string>
|
||||
<string name="add_custom_token_title">カスタムトークンの追加</string>
|
||||
<string name="add_tokens_title">トークンの管理</string>
|
||||
|
|
@ -445,6 +447,7 @@
|
|||
<string name="markets_token_details_price_performance">値動き</string>
|
||||
<string name="markets_token_details_repository">リポジトリ</string>
|
||||
<string name="markets_token_details_security_score">セキュリティ・スコア</string>
|
||||
<string name="markets_token_details_security_score_description">トークンのセキュリティ・スコアとは、ブロックチェーンやトークンのセキュリティ・レベルを様々な要因に基づいて評価する指標で、以下の情報源から集計されます。</string>
|
||||
<string name="markets_token_details_social">ソーシャル</string>
|
||||
<string name="markets_token_details_total_supply">総供給量</string>
|
||||
<string name="markets_token_details_total_supply_description">特定の暗号資産に存在しうるコインまたはトークンの最大数</string>
|
||||
|
|
@ -794,7 +797,7 @@
|
|||
<string name="staking_unbonding">ステーキング解約中</string>
|
||||
<string name="staking_unlocked_locked">ロック解除</string>
|
||||
<string name="staking_unlocking">ロック解除中</string>
|
||||
<string name="staking_unstake_amount_requirement_error">ステーキング金額は %s 以上である必要があります</string>
|
||||
<string name="staking_unstake_amount_requirement_error">ステーキング解除する金額は少なくとも%sである必要があります</string>
|
||||
<string name="staking_unstake_amount_validation_error">金額がステーキング残高を超えています</string>
|
||||
<string name="staking_unstaked">ステーキングされていない</string>
|
||||
<string name="staking_unstaking">ステーキング解除</string>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
<string name="action_buttons_swap_choose_token">Выберите токен</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">Нет доступных токенов для обмена с выбранным токеном, пожалуйста, выберите другой.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_title">Нет доступных пар</string>
|
||||
<string name="action_buttons_you_want_to_receive">Вы хотите получить</string>
|
||||
<string name="action_buttons_you_want_to_swap">Вы хотите обменять</string>
|
||||
<string name="add_custom_token_choose_network">Выберите сеть</string>
|
||||
<string name="add_custom_token_title">Добавить токен</string>
|
||||
<string name="add_tokens_title">Валюты</string>
|
||||
|
|
@ -311,7 +313,7 @@
|
|||
<string name="give_permission_current_transaction">Транзакция</string>
|
||||
<string name="give_permission_policy_type_footer">Укажите лимит доступа к выбранному токену</string>
|
||||
<string name="give_permission_rows_amount">Количество %s</string>
|
||||
<string name="give_permission_staking_footer">Функция подтверждения необходима для предоставления другому адресу разрешения на использование определенного количества ваших токенов.По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту StakeKit разрешение использовать ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете осуществить стейкинг токена.</string>
|
||||
<string name="give_permission_staking_footer">Функция подтверждения необходима для предоставления другому адресу разрешения на использование определенного количества ваших токенов. По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту StakeKit разрешение использовать ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете осуществить стейкинг токена.</string>
|
||||
<string name="give_permission_staking_subtitle">Чтобы продолжить, вам необходимо разрешить смарт контракту Polygon использовать ваш %s</string>
|
||||
<string name="give_permission_swap_subtitle" formatted="false">Чтобы продолжить, вам нужно разрешить смарт-контракту %1s использовать ваш %2s</string>
|
||||
<string name="give_permission_title">Дать разрешение</string>
|
||||
|
|
@ -768,6 +770,7 @@
|
|||
<string name="staking_notification_earn_rewards_text_weekly">Стейкайте безопасно и начинайте получать еженедельные награды.</string>
|
||||
<string name="staking_notification_earn_rewards_title">Получите награду за стейкинг</string>
|
||||
<string name="staking_notification_low_staked_balance_text">Оставшийся застейканный баланс будет слишком мал для вывода. Вам потребуется застейкать больше средств, чтобы достичь минимальной суммы для вывода.</string>
|
||||
<string name="staking_notification_low_staked_balance_title">Низкий баланс стейкинга</string>
|
||||
<string name="staking_notification_network_error_text">Стейкинг временно недоступен из-за проблем в сети. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="staking_notification_new_validator_funds_transfer">Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него.</string>
|
||||
<string name="staking_notification_restake_rewards_text">Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход</string>
|
||||
|
|
@ -800,6 +803,7 @@
|
|||
<string name="staking_rewards">Вознаграждения</string>
|
||||
<string name="staking_stake_locked">Стейкинг закрыт</string>
|
||||
<string name="staking_stake_more">Застейкать еще</string>
|
||||
<string name="staking_staked_amount">Застейканная сумма</string>
|
||||
<string name="staking_summary_description_text">Вы стейкаете %1$s и будете получать награду %2$s</string>
|
||||
<string name="staking_tap_to_unlock">Нажмите для разблокировки</string>
|
||||
<string name="staking_tap_to_unlock_or_vote">Нажмите для разблокировки</string>
|
||||
|
|
@ -933,7 +937,7 @@
|
|||
<string name="wallet_connect_error_with_framework_message">Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки.</string>
|
||||
<string name="wallet_connect_error_wrong_card_selected">Неверная карта или кольцо выбрана в приложении Tangem</string>
|
||||
<string name="wallet_connect_failed_to_build_tx">Не удалось создать транзакцию из данных Dapp. Код: %s</string>
|
||||
<string name="wallet_connect_generic_error_with_code">Произошла непредвиденная ошибка. Код ошибки: %d. Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки.</string>
|
||||
<string name="wallet_connect_generic_error_with_code">Произошла непредвиденная ошибка. Код ошибки: %d. Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать — обратитесь в службу поддержки.</string>
|
||||
<string name="wallet_connect_no_sessions_message">Нет открытых сессий WalletConnect</string>
|
||||
<string name="wallet_connect_no_sessions_title">Упс. Нет сессий.</string>
|
||||
<string name="wallet_connect_pairing_error">Не удалось создать пару WalletConnect: %1$s</string>
|
||||
|
|
@ -988,7 +992,7 @@
|
|||
<string name="warning_express_notification_invalid_reserve_amount_title">Сумма получения не может быть менее %s</string>
|
||||
<string name="warning_express_pair_unavailable_message">Это может произойти из-за того, что провайдер временно не предоставляет обмен выбранной вами пары. Пожалуйста, подождите некоторое время и попробуйте снова. (Код %s)</string>
|
||||
<string name="warning_express_pair_unavailable_title">Выбранная пара временно недоступна</string>
|
||||
<string name="warning_express_refresh_required_title">Cервис временно недоступен</string>
|
||||
<string name="warning_express_refresh_required_title">Сервис временно недоступен</string>
|
||||
<string name="warning_express_too_maximum_amount_title">Сумма для обмена должна быть не более %s</string>
|
||||
<string name="warning_express_too_minimal_amount_title">Сумма для обмена должна быть не менее %s</string>
|
||||
<string name="warning_express_wrong_amount_description">Пожалуйста, измените сумму для обмена</string>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<string name="action_buttons_swap_empty_search_message">Токен не знайдено у вашому портфелі? Перевірте ринки, щоб знайти та додати його для обміну</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">Немає доступних токенів для обміну з обраним токеном. Будь ласка, оберіть інший.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_title">Немає вільної пари</string>
|
||||
<string name="action_buttons_you_want_to_receive">Ви хочете отримати</string>
|
||||
<string name="action_buttons_you_want_to_swap">Ви хочете обміняти</string>
|
||||
<string name="add_custom_token_choose_network">Оберіть мережу</string>
|
||||
<string name="add_custom_token_title">Додати токен</string>
|
||||
<string name="add_tokens_title">Токени</string>
|
||||
|
|
@ -732,7 +734,7 @@
|
|||
<string name="staking_amount_tron_integer_error_unstaking">Сума зняття зі стейкінгу буде округлена до %1$s TRX через мережеві правила.</string>
|
||||
<string name="staking_claim_unstaked">Зняти кошти</string>
|
||||
<string name="staking_details_account_fee">Комісія за стейкінг-акаунт</string>
|
||||
<string name="staking_details_account_fee_info">Стейкінг-акаунт - це спеціальний рахунок, на якому зберігаються застейкані SOL токени. Він створюється, коли ви делегуєте свої токени валідатору для участі у перевірці транзакцій та отримання винагород. За створення стейкінг-акаунту стягується невелика комісія, яка повертається після завершення стейкінгу.</string>
|
||||
<string name="staking_details_account_fee_info">Стейкінг-акаунт — це спеціальний рахунок, на якому зберігаються застейкані SOL токени. Він створюється, коли ви делегуєте свої токени валідатору для участі у перевірці транзакцій та отримання винагород. За створення стейкінг-акаунту стягується невелика комісія, яка повертається після завершення стейкінгу.</string>
|
||||
<string name="staking_details_annual_percentage_rate">Процентна ставка</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">Річний відсоток, який ви можете отримати, беручи участь у стейкінгу.</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
|
|
@ -818,7 +820,6 @@
|
|||
<string name="staking_unbonding">Розблокування</string>
|
||||
<string name="staking_unlocked_locked">Розблокувати</string>
|
||||
<string name="staking_unlocking">Розблокування</string>
|
||||
<string name="staking_unstake_amount_requirement_error">Сума для стейкінгу має бути не менше %s</string>
|
||||
<string name="staking_unstake_amount_validation_error">Сума перевищує баланс стейкінгу</string>
|
||||
<string name="staking_unstaked">Вивід зі стейкінгу</string>
|
||||
<string name="staking_unstaking">Зняти зі стейкінгу</string>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
<string name="action_buttons_swap_empty_search_message">Token not found in your portfolio? Check the Markets to find and add it for swap</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">There are no available tokens to swap with the selected token. Please choose another one.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_title">No available pair</string>
|
||||
<string name="action_buttons_you_want_to_receive">You want to Receive</string>
|
||||
<string name="action_buttons_you_want_to_swap">You want to Swap</string>
|
||||
<string name="add_custom_token_choose_network">Choose network</string>
|
||||
<string name="add_custom_token_title">Add custom token</string>
|
||||
<string name="add_tokens_title">Manage tokens</string>
|
||||
|
|
@ -451,6 +453,7 @@
|
|||
<string name="markets_token_details_price_performance">Price performance</string>
|
||||
<string name="markets_token_details_repository">Repository</string>
|
||||
<string name="markets_token_details_security_score">Security score</string>
|
||||
<string name="markets_token_details_security_score_description">Security score of a token is a metric that assesses the security level of a blockchain or token based on various factors and is compiled from the sources listed below.</string>
|
||||
<string name="markets_token_details_social">Social</string>
|
||||
<string name="markets_token_details_total_supply">Total supply</string>
|
||||
<string name="markets_token_details_total_supply_description">The maximum number of coins or tokens that can ever exist for a particular cryptocurrency</string>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.res.Configuration
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
|
|
@ -26,6 +27,7 @@ fun AppBarWithBackButton(
|
|||
modifier: Modifier = Modifier,
|
||||
text: String? = null,
|
||||
@DrawableRes iconRes: Int? = null,
|
||||
containerColor: Color = Color.Transparent,
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = modifier,
|
||||
|
|
@ -34,6 +36,7 @@ fun AppBarWithBackButton(
|
|||
iconRes = iconRes ?: R.drawable.ic_back_24,
|
||||
onIconClicked = onBackClick,
|
||||
),
|
||||
containerColor = containerColor,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,4 +23,5 @@ data class ActionButtonConfig(
|
|||
val onLongClick: (() -> TextReference?)? = null,
|
||||
val enabled: Boolean = true,
|
||||
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.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -77,7 +78,6 @@ fun ActionButton(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun Button(
|
||||
config: ActionButtonConfig,
|
||||
|
|
@ -89,12 +89,33 @@ private fun Button(
|
|||
targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled,
|
||||
label = "Update background color",
|
||||
)
|
||||
val context = LocalContext.current
|
||||
Row(
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size36)
|
||||
.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(
|
||||
enabled = config.enabled,
|
||||
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, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -188,5 +222,12 @@ private class ActionStateProvider : CollectionPreviewParameterProvider<ActionBut
|
|||
enabled = false,
|
||||
onClick = {},
|
||||
),
|
||||
ActionButtonConfig(
|
||||
text = TextReference.Str(value = "Loading"),
|
||||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
enabled = false,
|
||||
onClick = {},
|
||||
isInProgress = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -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.extensions.TextReference
|
||||
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.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -145,14 +146,16 @@ private fun Modifier.tokenClickable(state: TokenItemState): Modifier = composed
|
|||
is TokenItemState.Unreachable,
|
||||
-> {
|
||||
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 {
|
||||
onClick == null && onLongClick == null -> this
|
||||
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 -> {
|
||||
combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
||||
combinedClickable(onClick = { onClick(state) }, onLongClick = onLongClick)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
|
|
@ -456,7 +459,7 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
tokenItemVisibleState.copy(
|
||||
iconState = coinIconState.copy(showCustomBadge = true),
|
||||
titleState = TokenItemState.TitleState.Content(
|
||||
text = "PolygonPolygonPolygonPolygonPolygonPolygon",
|
||||
text = stringReference(value = "PolygonPolygonPolygonPolygonPolygonPolygon"),
|
||||
hasPending = true,
|
||||
),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
||||
|
|
@ -473,41 +476,41 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
TokenItemState.Unreachable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.Unreachable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent("Token"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(stringReference(value = "Token")),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.NoAddress(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.NoAddress(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent("Token"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(value = stringReference(value = "Token")),
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.Draggable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "3 172,14 $"),
|
||||
),
|
||||
TokenItemState.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = false),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"),
|
||||
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
|
||||
|
|
@ -521,7 +524,7 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
TokenItemState.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = false),
|
||||
subtitle2State = TokenItemState.Subtitle2State.LabelContent(
|
||||
auditLabelUM = AuditLabelUM(
|
||||
|
|
@ -540,21 +543,21 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
TokenItemState.Loading(
|
||||
id = "Loading#1",
|
||||
iconState = customTokenIconState.copy(isGrayscale = true),
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
),
|
||||
tokenItemVisibleState.copy(
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon testnet"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon testnet")),
|
||||
iconState = tokenIconState.copy(isGrayscale = true),
|
||||
),
|
||||
tokenItemVisibleState.copy(
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
iconState = customTokenIconState.copy(
|
||||
tint = TangemColorPalette.White,
|
||||
background = TangemColorPalette.Black,
|
||||
),
|
||||
),
|
||||
tokenItemVisibleState.copy(
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
|
||||
iconState = customTokenIconState.copy(isGrayscale = true),
|
||||
),
|
||||
),
|
||||
|
|
@ -592,7 +595,10 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
TokenItemState.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
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),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"),
|
||||
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.SpacerW6
|
||||
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.TangemThemePreview
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
|
|
@ -38,7 +40,7 @@ internal fun TokenPrice(state: TokenPriceState?, modifier: Modifier = Modifier)
|
|||
)
|
||||
}
|
||||
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 -> {
|
||||
PriceText(text = DASH_SIGN, modifier = modifier)
|
||||
|
|
@ -158,7 +160,7 @@ private class TokenPriceChangeStateProvider : CollectionPreviewParameterProvider
|
|||
priceChangePercent = "2.5%",
|
||||
type = PriceChangeType.NEUTRAL,
|
||||
),
|
||||
TokenPriceState.TextContent(value = "Subtitle", isAvailable = true),
|
||||
TokenPriceState.TextContent(value = stringReference(value = "Subtitle"), isAvailable = true),
|
||||
TokenPriceState.Unknown,
|
||||
TokenPriceState.Loading,
|
||||
TokenPriceState.Locked,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
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.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.
|
||||
*/
|
||||
CurrencyNameText(
|
||||
name = state.text,
|
||||
name = state.text.resolveReference(),
|
||||
isAvailable = state.isAvailable,
|
||||
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.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
/** TokenItem component state */
|
||||
@Immutable
|
||||
|
|
@ -31,10 +32,10 @@ sealed class TokenItemState {
|
|||
abstract val subtitle2State: Subtitle2State?
|
||||
|
||||
/** 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 */
|
||||
abstract val onItemLongClick: (() -> Unit)?
|
||||
abstract val onItemLongClick: ((TokenItemState) -> Unit)?
|
||||
|
||||
/**
|
||||
* Loading token state
|
||||
|
|
@ -52,8 +53,8 @@ sealed class TokenItemState {
|
|||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading
|
||||
override val subtitle2State: Subtitle2State = Subtitle2State.Loading
|
||||
override val onItemClick: (() -> Unit)? = null
|
||||
override val onItemLongClick: (() -> Unit)? = null
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -67,8 +68,8 @@ sealed class TokenItemState {
|
|||
override val subtitleState: SubtitleState = SubtitleState.Locked
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Locked
|
||||
override val subtitle2State: Subtitle2State = Subtitle2State.Locked
|
||||
override val onItemClick: (() -> Unit)? = null
|
||||
override val onItemLongClick: (() -> Unit)? = null
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -90,8 +91,8 @@ sealed class TokenItemState {
|
|||
override val subtitleState: SubtitleState,
|
||||
override val fiatAmountState: FiatAmountState,
|
||||
override val subtitle2State: Subtitle2State,
|
||||
override val onItemClick: (() -> Unit)?,
|
||||
override val onItemLongClick: (() -> Unit)?,
|
||||
override val onItemClick: ((TokenItemState) -> Unit)?,
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||
) : TokenItemState()
|
||||
|
||||
/**
|
||||
|
|
@ -110,8 +111,8 @@ sealed class TokenItemState {
|
|||
) : TokenItemState() {
|
||||
override val subtitleState: SubtitleState? = null
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val onItemClick: (() -> Unit)? = null
|
||||
override val onItemLongClick: (() -> Unit)? = null
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -129,8 +130,8 @@ sealed class TokenItemState {
|
|||
override val iconState: CurrencyIconState,
|
||||
override val titleState: TitleState,
|
||||
override val subtitleState: SubtitleState? = null,
|
||||
override val onItemClick: (() -> Unit)?,
|
||||
override val onItemLongClick: (() -> Unit)?,
|
||||
override val onItemClick: ((TokenItemState) -> Unit)?,
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val subtitle2State: Subtitle2State? = null
|
||||
|
|
@ -150,18 +151,18 @@ sealed class TokenItemState {
|
|||
override val iconState: CurrencyIconState,
|
||||
override val titleState: TitleState,
|
||||
override val subtitleState: SubtitleState? = null,
|
||||
override val onItemLongClick: (() -> Unit)?,
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val subtitle2State: Subtitle2State? = null
|
||||
override val onItemClick: (() -> Unit)? = null
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class TitleState {
|
||||
|
||||
data class Content(
|
||||
val text: String,
|
||||
val text: TextReference,
|
||||
val hasPending: Boolean = false,
|
||||
val isAvailable: Boolean = true,
|
||||
) : TitleState()
|
||||
|
|
@ -180,7 +181,7 @@ sealed class TokenItemState {
|
|||
val type: PriceChangeType,
|
||||
) : 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()
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
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.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
|
|
@ -25,6 +26,9 @@ fun TokenListItem(state: TokensListItemUM, isBalanceHidden: Boolean, modifier: M
|
|||
is TokensListItemUM.NetworkGroupTitle -> {
|
||||
NetworkTitleItem(networkName = state.name.resolveReference(), modifier = modifier)
|
||||
}
|
||||
is TokensListItemUM.GroupTitle -> {
|
||||
GroupTitleItem(state, modifier)
|
||||
}
|
||||
is TokensListItemUM.Token -> {
|
||||
TokenItem(
|
||||
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.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -78,9 +77,7 @@ private fun BaseNetworkTitleItem(
|
|||
@Composable
|
||||
private fun DraggableIcon(reorderableTokenListState: ReorderableLazyListState) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size32)
|
||||
.detectReorder(reorderableTokenListState),
|
||||
modifier = Modifier.detectReorder(reorderableTokenListState),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
|
|
|
|||
|
|
@ -29,8 +29,17 @@ sealed interface TokensListItemUM {
|
|||
* @property id id
|
||||
* @property name network group name
|
||||
*/
|
||||
@Deprecated("Use GroupTitle instead") // TODO: [REDACTED_JIRA]
|
||||
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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ fun getActiveIconRes(blockchainId: String): Int {
|
|||
"casper", "casper/test" -> R.drawable.img_casper_22
|
||||
"xodex" -> R.drawable.img_xodex_22
|
||||
"canxium" -> R.drawable.img_canxium_22
|
||||
"chiliz", "chiliz/test" -> R.drawable.img_chiliz_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -162,6 +163,7 @@ fun getActiveIconResByCoinId(coinId: String): Int {
|
|||
"casper-network" -> R.drawable.img_casper_22
|
||||
"xodex" -> R.drawable.img_xodex_22
|
||||
"canxium" -> R.drawable.img_canxium_22
|
||||
"chiliz", "chiliz/test" -> R.drawable.img_chiliz_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -245,6 +247,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
|
|||
"casper", "casper/test" -> R.drawable.ic_casper_22
|
||||
"xodex" -> R.drawable.ic_xodex_22
|
||||
"canxium" -> R.drawable.ic_canxium_22
|
||||
"chiliz", "chiliz/test" -> R.drawable.ic_chiliz_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
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>
|
||||
|
|
@ -33,6 +33,7 @@ dependencies {
|
|||
|
||||
// region Others dependencies
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.blockchainsdk.compatibility.applyL2Compatibility
|
|||
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
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.getNetwork
|
||||
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.getOrThrow
|
||||
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.Companion.marketsQuoteFields
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.markets.*
|
||||
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.AtomicLong
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultMarketsTokenRepository(
|
||||
private val marketsApi: TangemTechMarketsApi,
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val tokenExchangesStore: RuntimeStateStore<List<TokenMarketExchangesResponse.Exchange>>,
|
||||
) : MarketsTokenRepository {
|
||||
|
||||
private fun createTokenMarketsFetcher(firstBatchSize: Int, nextBatchSize: Int) = LimitOffsetBatchFetcher(
|
||||
|
|
@ -266,9 +272,13 @@ internal class DefaultMarketsTokenRepository(
|
|||
|
||||
override suspend fun getTokenExchanges(tokenId: String): List<TokenMarketExchange> {
|
||||
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.di
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.markets.DefaultMarketsTokenRepository
|
||||
import com.tangem.datasource.api.markets.TangemTechMarketsApi
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -25,6 +27,7 @@ internal object MarketsDataModule {
|
|||
userWalletsStore: UserWalletsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
cacheRegistry: CacheRegistry,
|
||||
): MarketsTokenRepository {
|
||||
return DefaultMarketsTokenRepository(
|
||||
marketsApi = marketsApi,
|
||||
|
|
@ -32,6 +35,8 @@ internal object MarketsDataModule {
|
|||
dispatcherProvider = dispatchers,
|
||||
userWalletsStore = userWalletsStore,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
cacheRegistry = cacheRegistry,
|
||||
tokenExchangesStore = RuntimeStateStore(defaultValue = emptyList()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,19 +4,29 @@ import com.tangem.data.onramp.converters.CountryConverter
|
|||
import com.tangem.data.onramp.converters.CurrencyConverter
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
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.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.OnrampCurrency
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultOnrampRepository(
|
||||
private val onrampApi: OnrampApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : OnrampRepository {
|
||||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
private val countryConverter = CountryConverter()
|
||||
private val countryConverter = CountryConverter(currencyConverter)
|
||||
|
||||
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
|
||||
onrampApi.getCurrencies()
|
||||
|
|
@ -29,4 +39,48 @@ internal class DefaultOnrampRepository(
|
|||
.getOrThrow()
|
||||
.map(countryConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun getCountryByIp(): OnrampCountry = withContext(dispatchers.io) {
|
||||
onrampApi.getCountryByIp()
|
||||
.getOrThrow()
|
||||
.let(countryConverter::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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,14 @@ package com.tangem.data.onramp.converters
|
|||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
|
||||
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> {
|
||||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
internal class CountryConverter(
|
||||
private val currencyConverter: CurrencyConverter,
|
||||
) : TwoWayConverter<OnrampCountryDTO, OnrampCountry> {
|
||||
|
||||
override fun convert(value: OnrampCountryDTO) = OnrampCountry(
|
||||
id = "${value.alpha3}-${value.name}",
|
||||
name = value.name,
|
||||
code = value.code,
|
||||
image = value.image,
|
||||
|
|
@ -17,4 +18,14 @@ internal class CountryConverter : Converter<OnrampCountryDTO, OnrampCountry> {
|
|||
defaultCurrency = currencyConverter.convert(value.defaultCurrency),
|
||||
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.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(
|
||||
name = value.name,
|
||||
|
|
@ -12,4 +12,11 @@ internal class CurrencyConverter : Converter<OnrampCurrencyDTO, OnrampCurrency>
|
|||
image = value.image,
|
||||
precision = value.precision,
|
||||
)
|
||||
|
||||
override fun convertBack(value: OnrampCurrency): OnrampCurrencyDTO = OnrampCurrencyDTO(
|
||||
name = value.name,
|
||||
code = value.code,
|
||||
image = value.image,
|
||||
precision = value.precision,
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.onramp.di
|
|||
|
||||
import com.tangem.data.onramp.DefaultOnrampRepository
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -16,10 +17,15 @@ internal object OnrampDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampRepository(onrampApi: OnrampApi, dispatchers: CoroutineDispatcherProvider): OnrampRepository {
|
||||
fun provideOnrampRepository(
|
||||
onrampApi: OnrampApi,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): OnrampRepository {
|
||||
return DefaultOnrampRepository(
|
||||
onrampApi = onrampApi,
|
||||
dispatchers = dispatchers,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -48,7 +48,6 @@ import com.tangem.domain.tokens.model.Network
|
|||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
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.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -67,7 +66,6 @@ internal class DefaultStakingRepository(
|
|||
private val stakingBalanceStore: StakingBalanceStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val stakingFeatureToggle: StakingFeatureToggles,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
moshi: Moshi,
|
||||
|
|
@ -322,8 +320,6 @@ internal class DefaultStakingRepository(
|
|||
cryptoCurrency: CryptoCurrency,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
|
||||
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
|
|
@ -363,30 +359,26 @@ internal class DefaultStakingRepository(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<YieldBalance> = channelFlow {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
send(YieldBalance.Empty)
|
||||
} else {
|
||||
launch(dispatchers.io) {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
?: error("Could not get integrationId")
|
||||
stakingBalanceStore.get(userWalletId, address, integrationId)
|
||||
.distinctUntilChanged()
|
||||
.collectLatest {
|
||||
if (it != null) {
|
||||
send(yieldBalanceConverter.convert(it))
|
||||
} else {
|
||||
error("No yield balance available for currency ${cryptoCurrency.id.value}")
|
||||
}
|
||||
launch(dispatchers.io) {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
?: error("Could not get integrationId")
|
||||
stakingBalanceStore.get(userWalletId, address, integrationId)
|
||||
.distinctUntilChanged()
|
||||
.collectLatest {
|
||||
if (it != null) {
|
||||
send(yieldBalanceConverter.convert(it))
|
||||
} else {
|
||||
error("No yield balance available for currency ${cryptoCurrency.id.value}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchSingleYieldBalance(
|
||||
userWalletId,
|
||||
cryptoCurrency,
|
||||
)
|
||||
}
|
||||
withContext(dispatchers.io) {
|
||||
fetchSingleYieldBalance(
|
||||
userWalletId,
|
||||
cryptoCurrency,
|
||||
)
|
||||
}
|
||||
}.cancellable()
|
||||
|
||||
|
|
@ -394,21 +386,17 @@ internal class DefaultStakingRepository(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): YieldBalance = withContext(dispatchers.io) {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
YieldBalance.Empty
|
||||
} else {
|
||||
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
|
||||
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)]
|
||||
?: error("Could not get integrationId")
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
?: error("Could not get integrationId")
|
||||
|
||||
val result = stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId)
|
||||
?: return@withContext YieldBalance.Error
|
||||
val result = stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId)
|
||||
?: return@withContext YieldBalance.Error
|
||||
|
||||
yieldBalanceConverter.convert(result)
|
||||
}
|
||||
yieldBalanceConverter.convert(result)
|
||||
}
|
||||
|
||||
override suspend fun fetchMultiYieldBalance(
|
||||
|
|
@ -416,8 +404,6 @@ internal class DefaultStakingRepository(
|
|||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) return@withContext
|
||||
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
|
|
@ -474,19 +460,15 @@ internal class DefaultStakingRepository(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Flow<YieldBalanceList> = channelFlow {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
send(YieldBalanceList.Empty)
|
||||
} else {
|
||||
stakingBalanceStore.get(userWalletId)
|
||||
.onEach {
|
||||
val balances = yieldBalanceListConverter.convert(it)
|
||||
send(balances)
|
||||
}
|
||||
.launchIn(scope = this + dispatchers.io)
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false)
|
||||
stakingBalanceStore.get(userWalletId)
|
||||
.onEach {
|
||||
val balances = yieldBalanceListConverter.convert(it)
|
||||
send(balances)
|
||||
}
|
||||
.launchIn(scope = this + dispatchers.io)
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies, refresh = false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -494,13 +476,9 @@ internal class DefaultStakingRepository(
|
|||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): YieldBalanceList = withContext(dispatchers.io) {
|
||||
if (!stakingFeatureToggle.isStakingEnabled) {
|
||||
YieldBalanceList.Empty
|
||||
} else {
|
||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
|
||||
val result = stakingBalanceStore.getSyncOrNull(userWalletId) ?: return@withContext YieldBalanceList.Error
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import com.tangem.datasource.local.token.StakingYieldsStore
|
|||
import com.tangem.domain.staking.repositories.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -38,7 +37,6 @@ internal object StakingDataModule {
|
|||
stakingBalanceStore: StakingBalanceStore,
|
||||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
stakingFeatureToggle: StakingFeatureToggles,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
|
|
@ -49,7 +47,6 @@ internal object StakingDataModule {
|
|||
stakingBalanceStore = stakingBalanceStore,
|
||||
cacheRegistry = cacheRegistry,
|
||||
dispatchers = dispatchers,
|
||||
stakingFeatureToggle = stakingFeatureToggle,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
moshi = moshi,
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@ package com.tangem.data.tokens.di
|
|||
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.tokens.repository.*
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
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.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.quote.QuotesStore
|
||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
|
|
@ -26,23 +25,21 @@ internal object TokensDataModule {
|
|||
@Singleton
|
||||
fun provideCurrenciesRepository(
|
||||
tangemTechApi: TangemTechApi,
|
||||
tangemExpressApi: TangemExpressApi,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
expressAssetsStore: ExpressAssetsStore,
|
||||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
swapServiceLoader: SwapServiceLoader,
|
||||
): CurrenciesRepository {
|
||||
return DefaultCurrenciesRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
tangemExpressApi = tangemExpressApi,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
userWalletsStore = userWalletsStore,
|
||||
expressAssetsStore = expressAssetsStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
cacheRegistry = cacheRegistry,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
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
|
||||
@Singleton
|
||||
fun provideCurrencyChecksRepository(
|
||||
|
|
|
|||
|
|
@ -12,18 +12,14 @@ import com.tangem.data.tokens.utils.CustomTokensMerger
|
|||
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
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.models.UserTokensResponse
|
||||
import com.tangem.datasource.exchangeservice.swap.SwapServiceLoader
|
||||
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.datasource.local.token.ExpressAssetsStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.core.error.DataError
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
|
|
@ -46,12 +42,11 @@ import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency
|
|||
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
|
||||
internal class DefaultCurrenciesRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val tangemExpressApi: TangemExpressApi,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val expressAssetsStore: ExpressAssetsStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val swapServiceLoader: SwapServiceLoader,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : CurrenciesRepository {
|
||||
|
||||
|
|
@ -565,27 +560,7 @@ internal class DefaultCurrenciesRepository(
|
|||
userWalletId: UserWalletId,
|
||||
userTokens: UserTokensResponse,
|
||||
) {
|
||||
try {
|
||||
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}")
|
||||
}
|
||||
swapServiceLoader.update(userWalletId, userTokens)
|
||||
}
|
||||
|
||||
private suspend fun handleFetchTokensError(userWallet: UserWallet, e: ApiResponseError): UserTokensResponse {
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.data.tokens.repository
|
||||
|
||||
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
|
||||
import com.tangem.datasource.local.token.ExpressAssetsStore
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DefaultMarketCryptoCurrencyRepository(
|
||||
private val expressAssetsStore: ExpressAssetsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MarketCryptoCurrencyRepository {
|
||||
|
||||
override suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean {
|
||||
return getExchangeableFlag(userWalletId, cryptoCurrency) && !cryptoCurrency.isCustom
|
||||
}
|
||||
|
||||
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 = expressAssetsStore.getSyncOrNull(userWalletId)?.find {
|
||||
it.network == cryptoCurrency.network.backendId &&
|
||||
it.contractAddress.equals(contractAddress, ignoreCase = true)
|
||||
}
|
||||
|
||||
asset?.exchangeAvailable ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ dependencies {
|
|||
implementation(projects.common)
|
||||
implementation(projects.libs.auth)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package com.tangem.domain.exchange
|
||||
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Manager that holds info about available actions as Sell and Buy
|
||||
|
|
@ -11,4 +14,16 @@ interface RampStateManager {
|
|||
fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean
|
||||
|
||||
fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean
|
||||
|
||||
suspend fun availableForSwap(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean
|
||||
|
||||
suspend fun fetchBuyServiceData()
|
||||
|
||||
fun getBuyInitializationStatus(): Flow<Lce<Throwable, Any>>
|
||||
|
||||
suspend fun fetchSellServiceData()
|
||||
|
||||
fun getSellInitializationStatus(): Flow<Lce<Throwable, Any>>
|
||||
|
||||
fun getSwapInitializationStatus(userWalletId: UserWalletId): Flow<Lce<Throwable, Any>>
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
sealed interface OnrampAvailability {
|
||||
|
||||
data object Available : OnrampAvailability
|
||||
data class NotSupported(val country: OnrampCountry) : OnrampAvailability
|
||||
data class ConfirmResidency(val country: OnrampCountry) : OnrampAvailability
|
||||
}
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OnrampCountry(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val code: String,
|
||||
val image: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
data class OnrampCurrencies(val populars: List<OnrampCurrency>, val others: List<OnrampCurrency>)
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OnrampCurrency(
|
||||
val name: String,
|
||||
val code: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampAvailability
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class CheckOnrampAvailabilityUseCase(private val repository: OnrampRepository) {
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, OnrampAvailability> {
|
||||
return Either.catch {
|
||||
return@catch OnrampAvailability.Available
|
||||
repository.getDefaultCountrySync()?.let { savedCountry ->
|
||||
return@catch if (savedCountry.onrampAvailable) {
|
||||
OnrampAvailability.Available
|
||||
} else {
|
||||
OnrampAvailability.NotSupported(savedCountry)
|
||||
}
|
||||
}
|
||||
|
||||
val detectedCountry = repository.getCountryByIp()
|
||||
OnrampAvailability.ConfirmResidency(detectedCountry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class GetOnrampCountriesUseCase(private val onrampRepository: OnrampRepository) {
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, List<OnrampCountry>> {
|
||||
return Either.catch { onrampRepository.getCountries() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetOnrampCountryUseCase(private val repository: OnrampRepository) {
|
||||
|
||||
operator fun invoke(): Flow<Either<Throwable, OnrampCountry?>> {
|
||||
return repository.getDefaultCountry()
|
||||
.map<OnrampCountry?, Either<Throwable, OnrampCountry?>> { it.right() }
|
||||
.catch { emit(it.left()) }
|
||||
}
|
||||
|
||||
suspend fun invokeSync(): Either<Throwable, OnrampCountry> {
|
||||
return Either.catch { repository.getDefaultCountrySync() ?: repository.getCountryByIp() }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampCurrencies
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class GetOnrampCurrenciesUseCase(
|
||||
private val onrampRepository: OnrampRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, OnrampCurrencies> {
|
||||
return Either.catch {
|
||||
val currenciesList = onrampRepository.getCurrencies()
|
||||
val (populars, others) = currenciesList.toSet()
|
||||
.partition { popularFiatCodes.contains(it.code.uppercase()) }
|
||||
OnrampCurrencies(populars = populars, others = others)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val popularFiatCodes = setOf("USD", "EUR", "GBP", "CAD", "AUD", "HKD")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class OnrampSaveDefaultCountryUseCase(private val repository: OnrampRepository) {
|
||||
|
||||
suspend operator fun invoke(country: OnrampCountry) {
|
||||
repository.saveDefaultCountry(country)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class OnrampSaveDefaultCurrencyUseCase(private val repository: OnrampRepository) {
|
||||
|
||||
suspend operator fun invoke(currency: OnrampCurrency) {
|
||||
repository.saveDefaultCurrency(currency)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,16 @@ package com.tangem.domain.onramp.repositories
|
|||
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OnrampRepository {
|
||||
suspend fun getCurrencies(): List<OnrampCurrency>
|
||||
suspend fun getCountries(): List<OnrampCountry>
|
||||
suspend fun getCountryByIp(): OnrampCountry
|
||||
suspend fun saveDefaultCurrency(currency: OnrampCurrency)
|
||||
suspend fun getDefaultCurrencySync(): OnrampCurrency?
|
||||
fun getDefaultCurrency(): Flow<OnrampCurrency?>
|
||||
suspend fun saveDefaultCountry(country: OnrampCountry)
|
||||
suspend fun getDefaultCountrySync(): OnrampCountry?
|
||||
fun getDefaultCountry(): Flow<OnrampCountry?>
|
||||
}
|
||||
|
|
@ -7,12 +7,10 @@ import com.tangem.domain.staking.repositories.StakingRepository
|
|||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -27,12 +25,10 @@ import kotlinx.coroutines.flow.*
|
|||
class GetCryptoCurrencyActionsUseCase(
|
||||
private val rampManager: RampStateManager,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
|
|
@ -134,28 +130,26 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
}
|
||||
|
||||
// staking
|
||||
if (stakingFeatureToggles.isStakingEnabled) {
|
||||
if (isStakingAvailable(userWallet, cryptoCurrency)) {
|
||||
val yield = kotlin.runCatching {
|
||||
stakingRepository.getYield(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
}.getOrNull()
|
||||
activeList.add(
|
||||
TokenActionsState.ActionState.Stake(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.None,
|
||||
yield = yield,
|
||||
),
|
||||
if (isStakingAvailable(userWallet, cryptoCurrency)) {
|
||||
val yield = kotlin.runCatching {
|
||||
stakingRepository.getYield(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
} else {
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Stake(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name),
|
||||
yield = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.getOrNull()
|
||||
activeList.add(
|
||||
TokenActionsState.ActionState.Stake(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.None,
|
||||
yield = yield,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Stake(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name),
|
||||
yield = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// send
|
||||
|
|
@ -172,7 +166,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
// swap
|
||||
if (userWallet.isMultiCurrency) {
|
||||
if (
|
||||
marketCryptoCurrencyRepository.isExchangeable(userWallet.walletId, cryptoCurrency) &&
|
||||
rampManager.availableForSwap(userWallet.walletId, cryptoCurrency) &&
|
||||
cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote
|
||||
) {
|
||||
activeList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None))
|
||||
|
|
@ -271,9 +265,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
}
|
||||
actionsList.add(TokenActionsState.ActionState.Receive(scenario))
|
||||
}
|
||||
if (stakingFeatureToggles.isStakingEnabled) {
|
||||
actionsList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.Unreachable, null))
|
||||
}
|
||||
actionsList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.Unreachable, null))
|
||||
actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
|
||||
|
||||
return actionsList
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.*
|
||||
|
|
@ -26,7 +27,7 @@ class GetCurrencyWarningsUseCase(
|
|||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val swapRepository: SwapRepository,
|
||||
private val marketCryptoCurrencyRepository: MarketCryptoCurrencyRepository,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val promoRepository: PromoRepository,
|
||||
private val showSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
|
||||
|
|
@ -90,10 +91,10 @@ class GetCurrencyWarningsUseCase(
|
|||
val cryptoStatuses = operations.getCurrenciesStatusesSync()
|
||||
val promoBanner = promoRepository.getOkxPromoBanner()
|
||||
return combine(
|
||||
showSwapPromoTokenUseCase()
|
||||
flow = showSwapPromoTokenUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged(),
|
||||
flowOf(marketCryptoCurrencyRepository.isExchangeable(userWalletId, currency))
|
||||
flow2 = flowOf(rampStateManager.availableForSwap(userWalletId, currency))
|
||||
.conflate()
|
||||
.distinctUntilChanged(),
|
||||
) { shouldShowSwapPromo, isExchangeable ->
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
package com.tangem.domain.tokens.repository
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* MarketCryptoCurrencyRepository works with data from Tangem coins backend, CoinMarketCap etc
|
||||
*/
|
||||
interface MarketCryptoCurrencyRepository {
|
||||
|
||||
suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean
|
||||
}
|
||||
|
|
@ -38,6 +38,8 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV
|
|||
}
|
||||
}
|
||||
|
||||
fun getDefaultFallbackUrl(): String = Artwork.DEFAULT_IMG_URL
|
||||
|
||||
private fun getFallbackArtworkUrl(cardId: String): String {
|
||||
return when {
|
||||
cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
|
||||
|
|
@ -45,7 +47,7 @@ class GetCardImageUseCase(private val verifier: OnlineCardVerifier = OnlineCardV
|
|||
else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
|
||||
TwinCardNumber.First -> Artwork.TWIN_CARD_1_URL
|
||||
TwinCardNumber.Second -> Artwork.TWIN_CARD_2_URL
|
||||
else -> Artwork.DEFAULT_IMG_URL
|
||||
else -> getDefaultFallbackUrl()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,9 +62,9 @@ internal class OnboardingManageTokensModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
|
||||
combine(
|
||||
manageTokensListManager.currenciesToAdd,
|
||||
manageTokensListManager.currenciesToRemove,
|
||||
::handleChangedCurrencies,
|
||||
flow = manageTokensListManager.currenciesToAdd,
|
||||
flow2 = manageTokensListManager.currenciesToRemove,
|
||||
transform = ::handleChangedCurrencies,
|
||||
).launchIn(modelScope)
|
||||
|
||||
observeSearchQueryChanges()
|
||||
|
|
|
|||
|
|
@ -531,11 +531,11 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
|
||||
showBottomSheet(content = ExchangesBottomSheetContent.Loading(exchangesCount))
|
||||
|
||||
// Delay to show the bottom sheet
|
||||
delay(timeMillis = 800L)
|
||||
|
||||
val maybeExchanges = getTokenExchangesUseCase(tokenId = params.token.id)
|
||||
|
||||
// Delay to show the bottom sheet
|
||||
delay(timeMillis = 400L)
|
||||
|
||||
updateExchangeBSContent(maybeExchanges = maybeExchanges, exchangesCount = exchangesCount)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.ui.components.audits.AuditLabelUM
|
|||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.markets.TokenMarketExchange
|
||||
import com.tangem.domain.markets.TokenMarketExchange.TrustScore
|
||||
|
|
@ -26,7 +27,7 @@ internal object ExchangeItemStateConverter : Converter<TokenMarketExchange, Toke
|
|||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
titleState = TokenItemState.TitleState.Content(text = value.name),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value.name)),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
||||
text = BigDecimalFormatter.formatFiatPriceUncapped(
|
||||
fiatAmount = value.volumeInUsd,
|
||||
|
|
@ -35,7 +36,7 @@ internal object ExchangeItemStateConverter : Converter<TokenMarketExchange, Toke
|
|||
),
|
||||
),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(
|
||||
value = if (value.isCentralized) "CEX" else "DEX",
|
||||
value = stringReference(value = if (value.isCentralized) "CEX" else "DEX"),
|
||||
),
|
||||
subtitle2State = TokenItemState.Subtitle2State.LabelContent(
|
||||
auditLabelUM = value.trustScore.toAuditLabelUM(),
|
||||
|
|
|
|||
|
|
@ -176,9 +176,9 @@ private class ExchangesBottomSheetContentProvider : CollectionPreviewParameterPr
|
|||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
),
|
||||
titleState = TokenItemState.TitleState.Content(text = "OKX"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "OKX")),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "$67.52M"),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(value = "CEX"),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(value = stringReference(value = "CEX")),
|
||||
subtitle2State = TokenItemState.Subtitle2State.LabelContent(
|
||||
auditLabelUM = AuditLabelUM(
|
||||
text = stringReference("Caution"),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.markets.portfolio.impl.model
|
|||
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
|
|
@ -29,11 +30,11 @@ internal class PortfolioTokenUMConverter(
|
|||
override fun convert(value: PortfolioData.CryptoCurrencyData): PortfolioTokenUM {
|
||||
val tokenItemStateConverter = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
titleStateProvider = { TokenItemState.TitleState.Content(text = value.userWallet.name) },
|
||||
titleStateProvider = { TokenItemState.TitleState.Content(text = stringReference(value.userWallet.name)) },
|
||||
subtitleStateProvider = {
|
||||
TokenItemState.SubtitleState.TextContent(value = value.status.currency.name)
|
||||
TokenItemState.SubtitleState.TextContent(value = stringReference(value.status.currency.name))
|
||||
},
|
||||
onItemClick = onTokenItemClick,
|
||||
onItemClick = { _, status -> onTokenItemClick(status) },
|
||||
)
|
||||
|
||||
return PortfolioTokenUM(
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ internal class TokenActionsHandler @AssistedInject constructor(
|
|||
private fun onExchangeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
router.push(
|
||||
AppRoute.Swap(
|
||||
currency = cryptoCurrencyData.status.currency,
|
||||
currencyFrom = cryptoCurrencyData.status.currency,
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
isInitialReverseOrder = true,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie
|
|||
val onClick = state.tokenItemState.onItemClick
|
||||
if (onClick != null) {
|
||||
hapticManager.perform(TangemHapticEffect.View.ContextClick)
|
||||
onClick.invoke()
|
||||
onClick.invoke(it)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -81,8 +81,8 @@ private fun Preview(@PreviewParameter(PortfolioTokenUMProvider::class) tokenUM:
|
|||
PortfolioItem(
|
||||
state = tokenUM.copy(
|
||||
tokenItemState = when (tokenUM.tokenItemState) {
|
||||
is TokenItemState.Content -> tokenUM.tokenItemState.copy(onItemClick = onItemClick)
|
||||
is TokenItemState.Unreachable -> tokenUM.tokenItemState.copy(onItemClick = onItemClick)
|
||||
is TokenItemState.Content -> tokenUM.tokenItemState.copy(onItemClick = { onItemClick() })
|
||||
is TokenItemState.Unreachable -> tokenUM.tokenItemState.copy(onItemClick = { onItemClick() })
|
||||
else -> tokenUM.tokenItemState
|
||||
},
|
||||
isQuickActionsShown = quickActionsShown,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM
|
||||
|
|
@ -49,10 +50,12 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfol
|
|||
tokenItemState = TokenItemState.Content(
|
||||
id = "",
|
||||
iconState = CurrencyIconState.Locked,
|
||||
titleState = TokenItemState.TitleState.Content(text = "My wallet"),
|
||||
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "My wallet")),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "486,65 \$"),
|
||||
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "733,71097 MATIC"),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(value = "XRP Ledger token"),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(
|
||||
value = stringReference(value = "XRP Ledger token"),
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ interface OnboardingEntryComponent : ComposableContentComponent {
|
|||
|
||||
data class Params(
|
||||
val scanResponse: ScanResponse,
|
||||
val startBackupFlow: Boolean,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnboardingEntryComponent>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ dependencies {
|
|||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.wallets)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(projects.libs.tangemSdkApi)
|
||||
|
|
@ -54,6 +56,7 @@ dependencies {
|
|||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.timber)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -105,6 +105,14 @@ internal class DefaultOnboardingEntryComponent @AssistedInject constructor(
|
|||
}.saveIn(innerNavigationLinkJobHolder)
|
||||
}
|
||||
}
|
||||
|
||||
componentScope.launch {
|
||||
model.titleProvider.currentTitle.collect { title ->
|
||||
stepperComponent.state.update {
|
||||
it.copy(title = title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@ internal class OnboardingEntryModel @Inject constructor(
|
|||
return when (scanResponse.productType) {
|
||||
ProductType.Note -> TODO()
|
||||
ProductType.Twins -> TODO()
|
||||
ProductType.Wallet -> OnboardingRoute.Wallet12(
|
||||
ProductType.Wallet -> OnboardingRoute.MultiWallet(
|
||||
scanResponse = scanResponse,
|
||||
withSeedPhraseFlow = false,
|
||||
titleProvider = titleProvider,
|
||||
)
|
||||
ProductType.Wallet2 -> OnboardingRoute.Wallet12(
|
||||
ProductType.Wallet2 -> OnboardingRoute.MultiWallet(
|
||||
scanResponse = scanResponse,
|
||||
withSeedPhraseFlow = true,
|
||||
titleProvider = titleProvider,
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletCo
|
|||
import javax.inject.Inject
|
||||
|
||||
internal class OnboardingChildFactory @Inject constructor(
|
||||
private val wallet12ComponentFactory: OnboardingMultiWalletComponent.Factory,
|
||||
private val multiWalletComponentFactory: OnboardingMultiWalletComponent.Factory,
|
||||
) {
|
||||
|
||||
fun createChild(route: OnboardingRoute, childContext: AppComponentContext): Any {
|
||||
return when (route) {
|
||||
is OnboardingRoute.Wallet12 -> wallet12ComponentFactory.create(
|
||||
is OnboardingRoute.MultiWallet -> multiWalletComponentFactory.create(
|
||||
context = childContext,
|
||||
params = OnboardingMultiWalletComponent.Params(
|
||||
scanResponse = route.scanResponse,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ sealed class OnboardingRoute : Route {
|
|||
|
||||
data object None : OnboardingRoute()
|
||||
|
||||
data class Wallet12(
|
||||
data class MultiWallet(
|
||||
val titleProvider: TitleProvider,
|
||||
val scanResponse: ScanResponse,
|
||||
val withSeedPhraseFlow: Boolean,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ internal inline fun OnboardingEntry(
|
|||
animation = stackAnimation(slide()),
|
||||
) {
|
||||
when (it.configuration) {
|
||||
is OnboardingRoute.Wallet12 -> {
|
||||
is OnboardingRoute.MultiWallet -> {
|
||||
(it.instance as OnboardingMultiWalletComponent).Content(
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,12 +4,24 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.stack.*
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.decompose.navigation.inner.InnerNavigation
|
||||
import com.tangem.core.decompose.navigation.inner.InnerNavigationState
|
||||
import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||
import com.tangem.features.onboarding.v2.multiwallet.impl.ui.OnboardingMultiWallet
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -22,24 +34,82 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
|||
) : OnboardingMultiWalletComponent, AppComponentContext by context {
|
||||
|
||||
private val model: OnboardingMultiWalletModel = getOrCreateModel(params)
|
||||
private val stackNavigation = StackNavigation<OnboardingMultiWalletState.Step>()
|
||||
|
||||
override val innerNavigation: InnerNavigation = object : InnerNavigation {
|
||||
override val state = MutableStateFlow(
|
||||
Wallet12InnerNavigationState(1, 5), // TODO
|
||||
MultiWalletInnerNavigationState(1, 5),
|
||||
)
|
||||
|
||||
override fun pop(onComplete: (Boolean) -> Unit) {
|
||||
// TODO
|
||||
// TODO add warning dialog
|
||||
stackNavigation.pop(onComplete)
|
||||
}
|
||||
}
|
||||
|
||||
private val childStack: Value<ChildStack<OnboardingMultiWalletState.Step, MultiWalletChildComponent>> = childStack(
|
||||
key = "innerStack",
|
||||
source = stackNavigation,
|
||||
serializer = null,
|
||||
initialConfiguration = model.state.value.currentStep,
|
||||
handleBackButton = true,
|
||||
childFactory = { configuration, factoryContext -> createChild(configuration, childByContext(factoryContext)) },
|
||||
)
|
||||
|
||||
private fun createChild(
|
||||
step: OnboardingMultiWalletState.Step,
|
||||
childContext: AppComponentContext,
|
||||
): MultiWalletChildComponent {
|
||||
return when (step) {
|
||||
OnboardingMultiWalletState.Step.CreateWallet -> MultiWalletCreateWalletComponent(
|
||||
context = childContext,
|
||||
params = MultiWalletChildParams(
|
||||
multiWalletState = model.state,
|
||||
parentParams = params,
|
||||
),
|
||||
onDone = ::onStepDone,
|
||||
)
|
||||
OnboardingMultiWalletState.Step.AddBackupDevice -> MultiWalletBackupComponent(
|
||||
context = childContext,
|
||||
params = MultiWalletChildParams(
|
||||
multiWalletState = model.state,
|
||||
parentParams = params,
|
||||
),
|
||||
onDone = ::onStepDone,
|
||||
)
|
||||
OnboardingMultiWalletState.Step.FinishBackup -> TODO()
|
||||
OnboardingMultiWalletState.Step.Done -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onStepDone() {
|
||||
when (model.state.value.currentStep) {
|
||||
OnboardingMultiWalletState.Step.CreateWallet -> {
|
||||
stackNavigation.push(OnboardingMultiWalletState.Step.AddBackupDevice)
|
||||
// innerNavigation.state.value TODO
|
||||
}
|
||||
OnboardingMultiWalletState.Step.AddBackupDevice -> TODO()
|
||||
OnboardingMultiWalletState.Step.FinishBackup -> TODO()
|
||||
OnboardingMultiWalletState.Step.Done -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val uiState by model.uiState.collectAsStateWithLifecycle()
|
||||
val stackState by childStack.subscribeAsState()
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
OnboardingMultiWallet(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
state = uiState,
|
||||
childContent = { mdfr ->
|
||||
Children(
|
||||
stack = stackState,
|
||||
animation = stackAnimation(slide()),
|
||||
) {
|
||||
it.instance.Content(mdfr)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +122,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
|||
}
|
||||
}
|
||||
|
||||
data class Wallet12InnerNavigationState(
|
||||
data class MultiWalletInnerNavigationState(
|
||||
override val stackSize: Int,
|
||||
override val stackMaxSize: Int?,
|
||||
) : InnerNavigationState
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.onboarding.v2.multiwallet.impl.child
|
||||
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface MultiWalletChildComponent : ComposableContentComponent
|
||||
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