Updated on 2026-08-14
This commit is contained in:
commit
f4198d9a68
208 changed files with 3133 additions and 604 deletions
|
|
@ -81,7 +81,7 @@ dependencies {
|
|||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.datasource)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import com.tangem.blockchainsdk.BlockchainSDKFactory
|
|||
import com.tangem.blockchainsdk.signer.TransactionSignerFactory
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
|
|
@ -34,6 +35,7 @@ import com.tangem.tap.common.log.TangemAppLoggerInitializer
|
|||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
|
@ -52,6 +54,8 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getFeatureTogglesManager(): FeatureTogglesManager
|
||||
|
||||
fun getExcludedBlockchainsManager(): ExcludedBlockchainsManager
|
||||
|
||||
fun getNetworkConnectionManager(): NetworkConnectionManager
|
||||
|
||||
fun getCardScanningFeatureToggles(): CardScanningFeatureToggles
|
||||
|
|
@ -113,4 +117,6 @@ interface ApplicationEntryPoint {
|
|||
fun getOnrampFeatureToggles(): OnrampFeatureToggles
|
||||
|
||||
fun getOnboardingV2FeatureToggles(): OnboardingV2FeatureToggles
|
||||
|
||||
fun getCoroutineDispatcherProvider(): CoroutineDispatcherProvider
|
||||
}
|
||||
|
|
@ -74,6 +74,7 @@ import com.tangem.tap.common.redux.NotificationsHandler
|
|||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.features.intentHandler.IntentProcessor
|
||||
import com.tangem.tap.features.intentHandler.handlers.BackgroundScanIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.tap.features.intentHandler.handlers.WalletConnectLinkIntentHandler
|
||||
import com.tangem.tap.features.main.MainViewModel
|
||||
import com.tangem.tap.features.main.model.Toast
|
||||
|
|
@ -392,6 +393,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
|
||||
private fun initIntentHandlers() {
|
||||
val hasSavedWalletsProvider = { userWalletsListManager.hasUserWallets }
|
||||
intentProcessor.addHandler(OnPushClickedIntentHandler(analyticsEventsHandler))
|
||||
intentProcessor.addHandler(BackgroundScanIntentHandler(hasSavedWalletsProvider, lifecycleScope))
|
||||
intentProcessor.addHandler(WalletConnectLinkIntentHandler())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
|||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.analytics.models.Basic.TransactionSent.WalletForm
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.datasource.api.common.MoshiConverter
|
||||
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
|
|
@ -57,9 +58,11 @@ import com.tangem.tap.domain.tasks.product.DerivationsFinder
|
|||
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
|
||||
import com.tangem.tap.proxy.AppStateHolder
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.EntryPoints
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Store
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository as WalletConnect2Repository
|
||||
|
|
@ -88,6 +91,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val featureTogglesManager: FeatureTogglesManager
|
||||
get() = entryPoint.getFeatureTogglesManager()
|
||||
|
||||
private val excludedBlockchainsManager: ExcludedBlockchainsManager
|
||||
get() = entryPoint.getExcludedBlockchainsManager()
|
||||
|
||||
private val networkConnectionManager: NetworkConnectionManager
|
||||
get() = entryPoint.getNetworkConnectionManager()
|
||||
|
||||
|
|
@ -180,6 +186,9 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles
|
||||
get() = entryPoint.getOnboardingV2FeatureToggles()
|
||||
|
||||
private val dispatchers: CoroutineDispatcherProvider
|
||||
get() = entryPoint.getCoroutineDispatcherProvider()
|
||||
// endregion
|
||||
|
||||
override fun onCreate() {
|
||||
|
|
@ -200,9 +209,11 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
// TODO: Try to performance and user experience.
|
||||
// [REDACTED_JIRA]
|
||||
runBlocking {
|
||||
featureTogglesManager.init()
|
||||
|
||||
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
||||
awaitAll(
|
||||
async { featureTogglesManager.init() },
|
||||
async { excludedBlockchainsManager.init() },
|
||||
async { initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize()) },
|
||||
)
|
||||
}
|
||||
|
||||
loadNativeLibraries()
|
||||
|
|
@ -216,7 +227,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
|
||||
derivationsFinder = DerivationsFinder(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
appStateHolder.mainStore = store
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class MainScreen(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent("Main Screen", event, params) {
|
||||
|
||||
class ScreenOpened : MainScreen("Screen opened")
|
||||
class ButtonScanCard : MainScreen("Button - Scan Card")
|
||||
|
||||
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
|
||||
event = "Enable Biometric",
|
||||
params = mapOf("State" to state.value),
|
||||
)
|
||||
|
||||
class NoticeRateAppButton(result: AnalyticsParam.RateApp) : MainScreen(
|
||||
event = "Notice - Rate The App Button Tapped",
|
||||
params = mapOf("Result" to result.value),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.common.analytics.events
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
internal sealed class Push(event: String) : AnalyticsEvent(
|
||||
category = "Push",
|
||||
event = event,
|
||||
params = emptyMap(),
|
||||
error = null,
|
||||
) {
|
||||
|
||||
data object PushNotificationOpened : Push(event = "Push Notification Opened")
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ sealed class SignIn(
|
|||
error: Throwable? = null,
|
||||
) : AnalyticsEvent("Sign In", event, params, error) {
|
||||
|
||||
class ScreenOpened : SignIn(event = "Sing In Screen Opened")
|
||||
class ScreenOpened : SignIn(event = "Sign In Screen Opened")
|
||||
|
||||
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
|
||||
class ButtonCardSignIn : SignIn(event = "Button - Card Sign In")
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ internal sealed class WalletConnect(
|
|||
) : AnalyticsEvent("Wallet Connect", event, params, error) {
|
||||
|
||||
class ScreenOpened : WalletConnect(event = "WC Screen Opened")
|
||||
class NewSessionEstablished(dAppName: String, dAppUrl: String) : WalletConnect(
|
||||
class NewSessionEstablished(dAppName: String, dAppUrl: String, blockchainNames: List<String>) : WalletConnect(
|
||||
event = "New Session Established",
|
||||
params = mapOf(
|
||||
AnalyticsParam.DAPP_NAME to dAppName,
|
||||
AnalyticsParam.DAPP_URL to dAppUrl,
|
||||
AnalyticsParam.BLOCKCHAIN to blockchainNames.joinToString(","),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
|||
import com.tangem.tap.common.analytics.converters.ParamCardCurrencyConverter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -31,7 +30,7 @@ class CardContextInterceptor(
|
|||
|
||||
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean {
|
||||
return when (event) {
|
||||
is IntroductionProcess.ButtonScanCard, is MainScreen.ButtonScanCard -> false
|
||||
is IntroductionProcess.ButtonScanCard -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.extensions.remove
|
||||
|
||||
fun Blockchain.getNetworkStandardName(): String {
|
||||
return when (this) {
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20"
|
||||
Blockchain.BSC, Blockchain.BSCTestnet -> "BEP20"
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> "BEP2"
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> "TRC20"
|
||||
Blockchain.TON -> "TON"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
val Blockchain.networkNameWithoutTestnet
|
||||
get() = this.getNetworkName().remove(" Testnet")
|
||||
|
|
@ -18,6 +18,7 @@ import com.google.firebase.messaging.RemoteMessage
|
|||
import com.tangem.domain.common.LogConfig
|
||||
import com.tangem.tap.MainActivity
|
||||
import com.tangem.tap.common.images.createCoilImageLoader
|
||||
import com.tangem.tap.features.intentHandler.handlers.OnPushClickedIntentHandler
|
||||
import com.tangem.wallet.R
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ internal class TangemPushNotificationService : FirebaseMessagingService() {
|
|||
|
||||
// TODO refactoring: [REDACTED_JIRA]
|
||||
val intent = Intent(applicationContext, MainActivity::class.java)
|
||||
intent.putExtra(OnPushClickedIntentHandler.IS_OPENED_FROM_PUSH, true)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
/* context = */ this,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.tap.common.url
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
|
|
@ -34,6 +35,8 @@ internal class CustomTabsUrlOpener : UrlOpener {
|
|||
)
|
||||
.build()
|
||||
|
||||
customTabsIntent.intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
||||
customTabsIntent.launchUrl(context, Uri.parse(url))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object OnrampDomainModule {
|
||||
|
|
@ -125,4 +126,35 @@ internal object OnrampDomainModule {
|
|||
fun provideGetOnrampQuotesUseCase(onrampRepository: OnrampRepository): GetOnrampQuotesUseCase {
|
||||
return GetOnrampQuotesUseCase(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampSelectedPaymentMethodUseCase(
|
||||
onrampRepository: OnrampRepository,
|
||||
): GetOnrampSelectedPaymentMethodUseCase {
|
||||
return GetOnrampSelectedPaymentMethodUseCase(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampProviderWithQuoteUseCase(
|
||||
onrampRepository: OnrampRepository,
|
||||
): GetOnrampProviderWithQuoteUseCase {
|
||||
return GetOnrampProviderWithQuoteUseCase(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideOnrampSaveSelectedPaymentMethod(onrampRepository: OnrampRepository): OnrampSaveSelectedPaymentMethod {
|
||||
return OnrampSaveSelectedPaymentMethod(onrampRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetOnrampRedirectUrlUseCase(
|
||||
onrampRepository: OnrampRepository,
|
||||
transactionRepository: OnrampTransactionRepository,
|
||||
): GetOnrampRedirectUrlUseCase {
|
||||
return GetOnrampRedirectUrlUseCase(onrampRepository, transactionRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.domain.scanCard
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
|
||||
/**
|
||||
* Add custom token feature toggles
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.common.authentication.storage.AuthenticatedStorage
|
|||
import com.tangem.common.json.TangemSdkAdapter
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.models.scan.serialization.*
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||
|
|
@ -19,7 +20,6 @@ import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricU
|
|||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.utils.json.*
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.utils
|
||||
|
|
@ -267,7 +267,7 @@ internal class DefaultLegacyWalletConnectRepository(
|
|||
)
|
||||
}
|
||||
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>) {
|
||||
override fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>, blockchainNames: List<String>) {
|
||||
val sessionProposal: Wallet.Model.SessionProposal = requireNotNull(this.sessionProposal)
|
||||
|
||||
val userChains = userNamespaces.flatMap { namespace ->
|
||||
|
|
@ -311,6 +311,7 @@ internal class DefaultLegacyWalletConnectRepository(
|
|||
WalletConnect.NewSessionEstablished(
|
||||
dAppName = sessionProposal.name,
|
||||
dAppUrl = sessionProposal.url,
|
||||
blockchainNames = blockchainNames,
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.tap.domain.walletconnect2.di
|
|||
import android.app.Application
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ interface LegacyWalletConnectRepository {
|
|||
|
||||
fun disconnect(topic: String)
|
||||
|
||||
fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>)
|
||||
fun approve(userNamespaces: Map<NetworkNamespace, List<Account>>, blockchainNames: List<String>)
|
||||
|
||||
fun reject()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.domain.walletconnect2.domain
|
||||
|
||||
import arrow.core.flatten
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -236,8 +237,12 @@ class WalletConnectInteractor(
|
|||
blockchainHelper.getNamespaceFromFullChainIdOrNull(account.chainId)
|
||||
?.let { NetworkNamespace(it) }
|
||||
}.filterNotNull()
|
||||
|
||||
val blockchainNames = blockchainHelper.chainIdsToBlockchains(userNamespaces.values.flatten().map { it.chainId })
|
||||
.map { it.fullName }
|
||||
walletConnectRepository.approve(
|
||||
userNamespaces = userNamespaces,
|
||||
blockchainNames = blockchainNames,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.domain.walletconnect2.toggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
|
||||
internal class WalletConnectFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.tap.features.home.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeFeatureToggles @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.features.intentHandler.handlers
|
||||
|
||||
import android.content.Intent
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.tap.common.analytics.events.Push
|
||||
import com.tangem.tap.features.intentHandler.IntentHandler
|
||||
|
||||
internal class OnPushClickedIntentHandler(val analyticsEventHandler: AnalyticsEventHandler) : IntentHandler {
|
||||
|
||||
override fun handleIntent(intent: Intent?, isFromForeground: Boolean): Boolean {
|
||||
val fromPush = intent?.extras?.getBoolean(IS_OPENED_FROM_PUSH) ?: false
|
||||
|
||||
return if (fromPush) {
|
||||
analyticsEventHandler.send(Push.PushNotificationOpened)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val IS_OPENED_FROM_PUSH = "IS_OPENED_FROM_PUSH"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,11 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
|
|
@ -110,7 +110,11 @@ internal class SaveWalletMiddleware {
|
|||
// TODO: Remove after onboarding refactoring
|
||||
Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.On))
|
||||
} else {
|
||||
Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.On))
|
||||
Analytics.send(
|
||||
MainScreenAnalyticsEvent.EnableBiometrics(
|
||||
state = com.tangem.core.analytics.models.AnalyticsParam.OnOffState.On,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -148,7 +152,11 @@ internal class SaveWalletMiddleware {
|
|||
// TODO: Remove after onboarding refactoring
|
||||
Analytics.send(Onboarding.EnableBiometrics(AnalyticsParam.OnOffState.Off))
|
||||
} else {
|
||||
Analytics.send(MainScreen.EnableBiometrics(AnalyticsParam.OnOffState.Off))
|
||||
Analytics.send(
|
||||
MainScreenAnalyticsEvent.EnableBiometrics(
|
||||
com.tangem.core.analytics.models.AnalyticsParam.OnOffState.Off,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,14 +56,14 @@ object TradeCryptoMiddleware {
|
|||
private fun proceedBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
||||
val isOnrampEnabled = store.inject(DaggerGraphState::onrampFeatureToggles).isFeatureEnabled
|
||||
if (isOnrampEnabled) {
|
||||
proceedWithOnramp(action.cryptoCurrencyStatus.currency)
|
||||
proceedWithOnramp(action.userWallet.walletId, action.cryptoCurrencyStatus.currency)
|
||||
} else {
|
||||
proceedWithLegacyBuyAction(state, action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithOnramp(cryptoCurrency: CryptoCurrency) {
|
||||
store.dispatchNavigationAction { push(AppRoute.Onramp(cryptoCurrency)) }
|
||||
private fun proceedWithOnramp(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency) {
|
||||
store.dispatchNavigationAction { push(AppRoute.Onramp(userWalletId = userWalletId, currency = cryptoCurrency)) }
|
||||
}
|
||||
|
||||
private fun proceedWithLegacyBuyAction(state: () -> AppState?, action: TradeCryptoAction.Buy) {
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ internal class ChildFactory @Inject constructor(
|
|||
is AppRoute.Onramp -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = OnrampComponent.Params(route.currency),
|
||||
params = OnrampComponent.Params(userWalletId = route.userWalletId, cryptoCurrency = route.currency),
|
||||
componentFactory = onrampComponentFactory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -295,7 +295,10 @@ sealed class AppRoute(val path: String) : Route {
|
|||
}
|
||||
|
||||
@Serializable
|
||||
data class Onramp(val currency: CryptoCurrency) : AppRoute(path = "/onramp/${currency.symbol}"), RouteBundleParams {
|
||||
data class Onramp(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
) : AppRoute(path = "/onramp/${userWalletId.stringValue}/${currency.symbol}"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
|
||||
|
|
@ -322,6 +325,7 @@ sealed class AppRoute(val path: String) : Route {
|
|||
) : AppRoute(path = "/swap_crypto/${userWalletId.stringValue}")
|
||||
|
||||
// Onboarding V2
|
||||
@Serializable
|
||||
data class Onboarding(
|
||||
val scanResponse: ScanResponse,
|
||||
val startFromBackup: Boolean,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ sealed class AnalyticsParam {
|
|||
data object MyWallets : ScreensSources("My Wallets")
|
||||
data object Token : ScreensSources("Token")
|
||||
data object Stories : ScreensSources("Stories")
|
||||
data object Buy : ScreensSources("Buy")
|
||||
data object Swap : ScreensSources("Swap")
|
||||
data object Sell : ScreensSources("Sell")
|
||||
}
|
||||
|
||||
sealed class TxSentFrom(val value: String) {
|
||||
|
|
@ -161,6 +164,12 @@ sealed class AnalyticsParam {
|
|||
}
|
||||
}
|
||||
|
||||
enum class Status(val value: String) {
|
||||
Success(value = "Success"),
|
||||
Error(value = "Error"),
|
||||
Pending(value = "Pending"),
|
||||
}
|
||||
|
||||
companion object Key {
|
||||
const val BLOCKCHAIN = "blockchain"
|
||||
const val TOKEN_PARAM = "Token"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.core.analytics.models.event
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
|
||||
/**
|
||||
* Main screen analytics event
|
||||
*
|
||||
* @param event event name
|
||||
* @param params params
|
||||
*/
|
||||
sealed class MainScreenAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = mapOf(),
|
||||
) : AnalyticsEvent(category = "Main Screen", event = event, params = params) {
|
||||
|
||||
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreenAnalyticsEvent(
|
||||
event = "Enable Biometric",
|
||||
params = mapOf("State" to state.value),
|
||||
)
|
||||
|
||||
// region Action Buttons feature
|
||||
data class ButtonBuy(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||
event = "Button - Buy",
|
||||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||
)
|
||||
|
||||
data class ButtonSwap(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||
event = "Button - Swap",
|
||||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||
)
|
||||
|
||||
data class ButtonSell(val status: AnalyticsParam.Status) : MainScreenAnalyticsEvent(
|
||||
event = "Button - Sell",
|
||||
params = mapOf(AnalyticsParam.STATUS to status.value),
|
||||
)
|
||||
|
||||
data object BuyScreenOpened : MainScreenAnalyticsEvent(event = "Buy Screen Opened")
|
||||
|
||||
data object SwapScreenOpened : MainScreenAnalyticsEvent(event = "Swap Screen Opened")
|
||||
|
||||
data object SellScreenOpened : MainScreenAnalyticsEvent(event = "Sell Screen Opened")
|
||||
|
||||
data class BuyTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
|
||||
event = "Buy Token Clicked",
|
||||
params = mapOf(TOKEN_PARAM to currencySymbol),
|
||||
)
|
||||
|
||||
data class SellTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
|
||||
event = "Sell Token Clicked",
|
||||
params = mapOf(TOKEN_PARAM to currencySymbol),
|
||||
)
|
||||
|
||||
data class SwapTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
|
||||
event = "Swap Token Clicked",
|
||||
params = mapOf(TOKEN_PARAM to currencySymbol),
|
||||
)
|
||||
|
||||
data class ReceiveTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
|
||||
event = "Receive Token Clicked",
|
||||
params = mapOf(TOKEN_PARAM to currencySymbol),
|
||||
)
|
||||
|
||||
data class RemoveTokenClicked(val currencySymbol: String) : MainScreenAnalyticsEvent(
|
||||
event = "Remove Button Clicked",
|
||||
params = mapOf(TOKEN_PARAM to currencySymbol),
|
||||
)
|
||||
|
||||
data class ButtonClose(val source: AnalyticsParam.ScreensSources) : MainScreenAnalyticsEvent(
|
||||
event = "Button - Close",
|
||||
params = mapOf(AnalyticsParam.SOURCE to source.value),
|
||||
)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.core.featuretoggles"
|
||||
namespace = "com.tangem.core.configtoggle"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
[
|
||||
{
|
||||
"name": "",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NEXA",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.configtoggle.blockchain
|
||||
|
||||
interface ExcludedBlockchainsManager {
|
||||
|
||||
val excludedBlockchainsIds: Set<String>
|
||||
|
||||
suspend fun init()
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.core.configtoggle.blockchain
|
||||
|
||||
interface MutableExcludedBlockchainsManager : ExcludedBlockchainsManager {
|
||||
|
||||
suspend fun excludeBlockchain(mainnetId: String, isExcluded: Boolean)
|
||||
|
||||
fun isMatchLocalConfig(): Boolean
|
||||
|
||||
suspend fun recoverLocalConfig()
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.tangem.core.configtoggle.blockchain.impl
|
||||
|
||||
import com.tangem.core.configtoggle.blockchain.MutableExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.storage.TogglesStorage
|
||||
import com.tangem.core.configtoggle.utils.associateToggles
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
|
||||
internal class DefaultExcludedBlockchainsManager(
|
||||
private val localTogglesStorage: TogglesStorage,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val versionProvider: VersionProvider,
|
||||
) : MutableExcludedBlockchainsManager {
|
||||
|
||||
private var isInitialized: Boolean = false
|
||||
|
||||
private lateinit var currentExcludedBlockchainsIds: Set<String>
|
||||
private lateinit var localExcludedBlockchainsIds: Set<String>
|
||||
|
||||
override val excludedBlockchainsIds: Set<String>
|
||||
get() {
|
||||
if (!isInitialized) error("ExcludedBlockchainsManager is not initialized")
|
||||
|
||||
return currentExcludedBlockchainsIds
|
||||
}
|
||||
|
||||
override suspend fun init() {
|
||||
localTogglesStorage.populate(path = "configs/excluded_blockchains_config")
|
||||
|
||||
val storedExcludedBlockchainsIds = appPreferencesStore.getSyncOrDefault(
|
||||
key = PreferencesKeys.EXCLUDED_BLOCKCHAINS_KEY,
|
||||
default = emptySet(),
|
||||
)
|
||||
|
||||
localExcludedBlockchainsIds = localTogglesStorage.toggles
|
||||
.associateToggles(currentVersion = versionProvider.get().orEmpty())
|
||||
.filterValues { isIncluded -> !isIncluded }
|
||||
.keys
|
||||
|
||||
currentExcludedBlockchainsIds = localExcludedBlockchainsIds + storedExcludedBlockchainsIds
|
||||
|
||||
isInitialized = true
|
||||
}
|
||||
|
||||
override suspend fun excludeBlockchain(mainnetId: String, isExcluded: Boolean) {
|
||||
if (isExcluded) {
|
||||
currentExcludedBlockchainsIds += mainnetId
|
||||
} else {
|
||||
currentExcludedBlockchainsIds -= mainnetId
|
||||
}
|
||||
|
||||
storeCurrent()
|
||||
}
|
||||
|
||||
override fun isMatchLocalConfig(): Boolean {
|
||||
return currentExcludedBlockchainsIds == localExcludedBlockchainsIds
|
||||
}
|
||||
|
||||
override suspend fun recoverLocalConfig() {
|
||||
currentExcludedBlockchainsIds = localExcludedBlockchainsIds
|
||||
|
||||
storeCurrent()
|
||||
}
|
||||
|
||||
private suspend fun storeCurrent() {
|
||||
appPreferencesStore.store(
|
||||
PreferencesKeys.EXCLUDED_BLOCKCHAINS_KEY,
|
||||
currentExcludedBlockchainsIds,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.core.configtoggle.di
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.core.configtoggle.BuildConfig
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.blockchain.MutableExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.blockchain.impl.DefaultExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.version.DefaultVersionProvider
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object ExcludedBlockchainsManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideExcludedBlockchainsManager(
|
||||
@ApplicationContext context: Context,
|
||||
assetLoader: AssetLoader,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): ExcludedBlockchainsManager {
|
||||
val localTogglesStorage = LocalTogglesStorage(assetLoader)
|
||||
val versionProvider = DefaultVersionProvider(context)
|
||||
|
||||
return DefaultExcludedBlockchainsManager(
|
||||
localTogglesStorage,
|
||||
appPreferencesStore,
|
||||
versionProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMutableExcludedBlockchainsManager(
|
||||
manager: ExcludedBlockchainsManager,
|
||||
): MutableExcludedBlockchainsManager? {
|
||||
if (!BuildConfig.TESTER_MENU_ENABLED) return null
|
||||
|
||||
return manager as MutableExcludedBlockchainsManager
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.core.featuretoggle.di
|
||||
package com.tangem.core.configtoggle.di
|
||||
|
||||
import android.content.Context
|
||||
import com.tangem.core.featuretoggle.manager.DevFeatureTogglesManager
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.core.featuretoggle.manager.ProdFeatureTogglesManager
|
||||
import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage
|
||||
import com.tangem.core.featuretoggle.version.DefaultVersionProvider
|
||||
import com.tangem.core.featuretoggles.BuildConfig
|
||||
import com.tangem.core.configtoggle.BuildConfig
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.impl.DevFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.impl.ProdFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.storage.LocalTogglesStorage
|
||||
import com.tangem.core.configtoggle.version.DefaultVersionProvider
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import dagger.Module
|
||||
|
|
@ -27,18 +27,18 @@ internal object FeatureTogglesManagerModule {
|
|||
assetLoader: AssetLoader,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
): FeatureTogglesManager {
|
||||
val localFeatureTogglesStorage = LocalFeatureTogglesStorage(assetLoader)
|
||||
val localTogglesStorage = LocalTogglesStorage(assetLoader)
|
||||
val versionProvider = DefaultVersionProvider(context)
|
||||
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
DevFeatureTogglesManager(
|
||||
localFeatureTogglesStorage = localFeatureTogglesStorage,
|
||||
localTogglesStorage = localTogglesStorage,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
versionProvider = versionProvider,
|
||||
)
|
||||
} else {
|
||||
ProdFeatureTogglesManager(
|
||||
localFeatureTogglesStorage = localFeatureTogglesStorage,
|
||||
localTogglesStorage = localTogglesStorage,
|
||||
versionProvider = versionProvider,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.featuretoggle.manager
|
||||
package com.tangem.core.configtoggle.feature
|
||||
|
||||
/**
|
||||
* Component for getting information about the availability of feature toggles
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.featuretoggle.manager
|
||||
package com.tangem.core.configtoggle.feature
|
||||
|
||||
/**
|
||||
* Component for change information about the availability of feature toggles
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.core.featuretoggle.manager
|
||||
package com.tangem.core.configtoggle.feature.impl
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.tangem.core.featuretoggle.storage.FeatureTogglesStorage
|
||||
import com.tangem.core.featuretoggle.utils.associateToggles
|
||||
import com.tangem.core.featuretoggle.version.VersionProvider
|
||||
import com.tangem.core.configtoggle.feature.MutableFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.storage.TogglesStorage
|
||||
import com.tangem.core.configtoggle.utils.associateToggles
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
|
|
@ -13,12 +14,12 @@ import kotlin.properties.Delegates
|
|||
/**
|
||||
* Feature toggles manager implementation in DEV build
|
||||
*
|
||||
* @property localFeatureTogglesStorage local feature toggles storage
|
||||
* @property localTogglesStorage local feature toggles storage
|
||||
* @property appPreferencesStore application local store
|
||||
* @property versionProvider application version provider
|
||||
*/
|
||||
internal class DevFeatureTogglesManager(
|
||||
private val localFeatureTogglesStorage: FeatureTogglesStorage,
|
||||
private val localTogglesStorage: TogglesStorage,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val versionProvider: VersionProvider,
|
||||
) : MutableFeatureTogglesManager {
|
||||
|
|
@ -27,13 +28,13 @@ internal class DevFeatureTogglesManager(
|
|||
private var localFeatureTogglesMap: Map<String, Boolean> by Delegates.notNull()
|
||||
|
||||
override suspend fun init() {
|
||||
localFeatureTogglesStorage.init()
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
|
||||
val savedFeatureToggles = appPreferencesStore.getObjectSyncOrNull<Map<String, Boolean>>(
|
||||
key = PreferencesKeys.FEATURE_TOGGLES_KEY,
|
||||
) ?: emptyMap()
|
||||
|
||||
val localFeatureToggles = localFeatureTogglesStorage.featureToggles
|
||||
val localFeatureToggles = localTogglesStorage.toggles
|
||||
.associateToggles(currentVersion = versionProvider.get().orEmpty())
|
||||
|
||||
localFeatureTogglesMap = localFeatureToggles
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.core.configtoggle.feature.impl
|
||||
|
||||
internal object FeatureTogglesConstants {
|
||||
|
||||
const val LOCAL_CONFIG_PATH: String = "configs/feature_toggles_config"
|
||||
}
|
||||
|
|
@ -1,27 +1,28 @@
|
|||
package com.tangem.core.featuretoggle.manager
|
||||
package com.tangem.core.configtoggle.feature.impl
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.tangem.core.featuretoggle.storage.FeatureTogglesStorage
|
||||
import com.tangem.core.featuretoggle.utils.associateToggles
|
||||
import com.tangem.core.featuretoggle.version.VersionProvider
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.storage.TogglesStorage
|
||||
import com.tangem.core.configtoggle.utils.associateToggles
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Feature toggles manager implementation in PROD build
|
||||
*
|
||||
* @property localFeatureTogglesStorage local feature toggles storage
|
||||
* @property localTogglesStorage local feature toggles storage
|
||||
* @property versionProvider application version provider
|
||||
*/
|
||||
internal class ProdFeatureTogglesManager(
|
||||
private val localFeatureTogglesStorage: FeatureTogglesStorage,
|
||||
private val localTogglesStorage: TogglesStorage,
|
||||
private val versionProvider: VersionProvider,
|
||||
) : FeatureTogglesManager {
|
||||
|
||||
private var featureToggles: Map<String, Boolean> by Delegates.notNull()
|
||||
|
||||
override suspend fun init() {
|
||||
localFeatureTogglesStorage.init()
|
||||
featureToggles = localFeatureTogglesStorage.featureToggles
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
featureToggles = localTogglesStorage.toggles
|
||||
.associateToggles(currentVersion = versionProvider.get() ?: "")
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.core.configtoggle.storage
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Data model with information about config toggle
|
||||
*
|
||||
* @property name toggle name
|
||||
* @property version version in which the toggle will be enabled
|
||||
*
|
||||
* IMPORTANT: if the version is "undefined", it means that toggle is disabled!
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class ConfigToggle(
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "version") val version: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.core.configtoggle.storage
|
||||
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Storage implementation for storing local feature toggles.
|
||||
* Feature toggles are declared in file [LOCAL_CONFIG_PATH].
|
||||
*
|
||||
* @property assetLoader asset loader
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class LocalTogglesStorage(
|
||||
private val assetLoader: AssetLoader,
|
||||
) : TogglesStorage {
|
||||
|
||||
override var toggles: List<ConfigToggle> by Delegates.notNull()
|
||||
private set
|
||||
|
||||
override suspend fun populate(path: String) {
|
||||
toggles = assetLoader.loadList<ConfigToggle>(path)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.core.configtoggle.storage
|
||||
|
||||
/**
|
||||
* Component that initializes and stores a list of feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal interface TogglesStorage {
|
||||
|
||||
/** List of feature toggles */
|
||||
val toggles: List<ConfigToggle>
|
||||
|
||||
/** Populate the storage with toggles */
|
||||
suspend fun populate(path: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.core.configtoggle.utils
|
||||
|
||||
import com.tangem.core.configtoggle.storage.ConfigToggle
|
||||
import com.tangem.core.configtoggle.version.VersionAvailabilityContract
|
||||
|
||||
internal fun List<ConfigToggle>.associateToggles(currentVersion: String): Map<String, Boolean> {
|
||||
return associate { localToggle ->
|
||||
Pair(
|
||||
first = localToggle.name,
|
||||
second = VersionAvailabilityContract(currentVersion, localToggle.version),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.featuretoggle.version
|
||||
package com.tangem.core.configtoggle.version
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.featuretoggle.contract
|
||||
package com.tangem.core.configtoggle.version
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import timber.log.Timber
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.featuretoggle.contract
|
||||
package com.tangem.core.configtoggle.version
|
||||
|
||||
/**
|
||||
* Version contract to evaluate availability of feature toggle
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.core.featuretoggle.version
|
||||
package com.tangem.core.configtoggle.version
|
||||
|
||||
/** Application version provider */
|
||||
internal interface VersionProvider {
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.core.featuretoggle.contract
|
||||
package com.tangem.core.configtoggle.contract
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.configtoggle.version.VersionAvailabilityContract
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.core.featuretoggle.contract
|
||||
package com.tangem.core.configtoggle.contract
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.configtoggle.version.Version
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.core.featuretoggle.manager
|
||||
package com.tangem.core.configtoggle.manager
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.featuretoggle.storage.FeatureToggle
|
||||
import com.tangem.core.featuretoggle.storage.FeatureTogglesStorage
|
||||
import com.tangem.core.featuretoggle.utils.associateToggles
|
||||
import com.tangem.core.featuretoggle.version.VersionProvider
|
||||
import com.tangem.core.configtoggle.feature.impl.DevFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.feature.impl.FeatureTogglesConstants
|
||||
import com.tangem.core.configtoggle.storage.ConfigToggle
|
||||
import com.tangem.core.configtoggle.storage.TogglesStorage
|
||||
import com.tangem.core.configtoggle.utils.associateToggles
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
|
|
@ -19,13 +21,13 @@ import kotlin.collections.set
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
internal class DevFeatureTogglesManagerTest {
|
||||
internal class DevTogglesManagerTest {
|
||||
|
||||
private val localFeatureTogglesStorage = mockk<FeatureTogglesStorage>()
|
||||
private val localTogglesStorage = mockk<TogglesStorage>()
|
||||
private val appPreferenceStore = mockk<AppPreferencesStore>(relaxed = true)
|
||||
private val versionProvider = mockk<VersionProvider>()
|
||||
private val manager = DevFeatureTogglesManager(
|
||||
localFeatureTogglesStorage = localFeatureTogglesStorage,
|
||||
localTogglesStorage = localTogglesStorage,
|
||||
appPreferencesStore = appPreferenceStore,
|
||||
versionProvider = versionProvider,
|
||||
)
|
||||
|
|
@ -34,17 +36,17 @@ internal class DevFeatureTogglesManagerTest {
|
|||
fun `successfully initialize storage if shared prefs kept feature toggles`() = runTest {
|
||||
val currentVersion = "0.1.0"
|
||||
|
||||
coEvery { localFeatureTogglesStorage.init() } just Runs
|
||||
coEvery { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) } just Runs
|
||||
coEvery {
|
||||
appPreferenceStore.getObjectSyncOrNull<Map<String, Boolean>>(PreferencesKeys.FEATURE_TOGGLES_KEY)
|
||||
} returns savedFeatureTogglesMap
|
||||
coEvery { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles
|
||||
coEvery { localTogglesStorage.toggles } returns localFeatureToggles
|
||||
coEvery { versionProvider.get() } returns currentVersion
|
||||
|
||||
manager.init()
|
||||
|
||||
coVerifyOrder {
|
||||
localFeatureTogglesStorage.init()
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
versionProvider.get()
|
||||
}
|
||||
|
||||
|
|
@ -61,17 +63,17 @@ internal class DevFeatureTogglesManagerTest {
|
|||
fun `successfully initialize storage if shared prefs kept empty list`() = runTest {
|
||||
val currentVersion = "0.1.0"
|
||||
|
||||
coEvery { localFeatureTogglesStorage.init() } just Runs
|
||||
coEvery { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) } just Runs
|
||||
coEvery {
|
||||
appPreferenceStore.getObjectSyncOrNull<Map<String, Boolean>>(PreferencesKeys.FEATURE_TOGGLES_KEY)
|
||||
} returns emptyMap()
|
||||
coEvery { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles
|
||||
coEvery { localTogglesStorage.toggles } returns localFeatureToggles
|
||||
coEvery { versionProvider.get() } returns currentVersion
|
||||
|
||||
manager.init()
|
||||
|
||||
coVerifyOrder {
|
||||
localFeatureTogglesStorage.init()
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
versionProvider.get()
|
||||
}
|
||||
|
||||
|
|
@ -88,17 +90,17 @@ internal class DevFeatureTogglesManagerTest {
|
|||
fun `successfully initialize storage if shared prefs didn't keep feature toggles`() = runTest {
|
||||
val currentVersion = "0.1.0"
|
||||
|
||||
coEvery { localFeatureTogglesStorage.init() } just Runs
|
||||
coEvery { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) } just Runs
|
||||
coEvery {
|
||||
appPreferenceStore.getObjectSyncOrNull<Map<String, Boolean>>(PreferencesKeys.FEATURE_TOGGLES_KEY)
|
||||
} returns null
|
||||
coEvery { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles
|
||||
coEvery { localTogglesStorage.toggles } returns localFeatureToggles
|
||||
coEvery { versionProvider.get() } returns currentVersion
|
||||
|
||||
manager.init()
|
||||
|
||||
coVerifyOrder {
|
||||
localFeatureTogglesStorage.init()
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
versionProvider.get()
|
||||
}
|
||||
|
||||
|
|
@ -111,15 +113,15 @@ internal class DevFeatureTogglesManagerTest {
|
|||
|
||||
@Test
|
||||
fun `successfully initialize storage if versionProvider returns null`() = runTest {
|
||||
coEvery { localFeatureTogglesStorage.init() } just Runs
|
||||
coEvery { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) } just Runs
|
||||
coEvery { appPreferenceStore.getSyncOrNull(PreferencesKeys.FEATURE_TOGGLES_KEY) } returns savedFeatureToggles
|
||||
coEvery { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles
|
||||
coEvery { localTogglesStorage.toggles } returns localFeatureToggles
|
||||
coEvery { versionProvider.get() } returns null
|
||||
|
||||
manager.init()
|
||||
|
||||
coVerifyOrder {
|
||||
localFeatureTogglesStorage.init()
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
versionProvider.get()
|
||||
}
|
||||
|
||||
|
|
@ -222,8 +224,8 @@ internal class DevFeatureTogglesManagerTest {
|
|||
)
|
||||
|
||||
val localFeatureToggles = listOf(
|
||||
FeatureToggle(name = "INACTIVE_TEST_FEATURE_ENABLED", version = "undefined"),
|
||||
FeatureToggle(name = "ACTIVE2_TEST_FEATURE_ENABLED", version = "1.0.0"),
|
||||
ConfigToggle(name = "INACTIVE_TEST_FEATURE_ENABLED", version = "undefined"),
|
||||
ConfigToggle(name = "ACTIVE2_TEST_FEATURE_ENABLED", version = "1.0.0"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
package com.tangem.core.featuretoggle.manager
|
||||
package com.tangem.core.configtoggle.manager
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.core.featuretoggle.storage.FeatureToggle
|
||||
import com.tangem.core.featuretoggle.storage.FeatureTogglesStorage
|
||||
import com.tangem.core.featuretoggle.utils.associateToggles
|
||||
import com.tangem.core.featuretoggle.version.VersionProvider
|
||||
import com.tangem.core.configtoggle.feature.impl.FeatureTogglesConstants
|
||||
import com.tangem.core.configtoggle.feature.impl.ProdFeatureTogglesManager
|
||||
import com.tangem.core.configtoggle.storage.ConfigToggle
|
||||
import com.tangem.core.configtoggle.storage.TogglesStorage
|
||||
import com.tangem.core.configtoggle.utils.associateToggles
|
||||
import com.tangem.core.configtoggle.version.VersionProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -13,24 +15,24 @@ import org.junit.Test
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class ProdFeatureTogglesManagerTest {
|
||||
internal class ProdTogglesManagerTest {
|
||||
|
||||
private val localFeatureTogglesStorage = mockk<FeatureTogglesStorage>()
|
||||
private val localTogglesStorage = mockk<TogglesStorage>()
|
||||
private val versionProvider = mockk<VersionProvider>()
|
||||
private val manager = ProdFeatureTogglesManager(localFeatureTogglesStorage, versionProvider)
|
||||
private val manager = ProdFeatureTogglesManager(localTogglesStorage, versionProvider)
|
||||
|
||||
@Test
|
||||
fun `successfully initialize storage`() = runTest {
|
||||
val currentVersion = "1.0.0"
|
||||
|
||||
coEvery { localFeatureTogglesStorage.init() } just Runs
|
||||
every { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles
|
||||
coEvery { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) } just Runs
|
||||
every { localTogglesStorage.toggles } returns localFeatureToggles
|
||||
every { versionProvider.get() } returns currentVersion
|
||||
|
||||
manager.init()
|
||||
|
||||
coVerifyOrder {
|
||||
localFeatureTogglesStorage.init()
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
versionProvider.get()
|
||||
}
|
||||
|
||||
|
|
@ -40,14 +42,14 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
|
||||
@Test
|
||||
fun `successfully initialize storage if versionProvider returns null`() = runTest {
|
||||
coEvery { localFeatureTogglesStorage.init() } just Runs
|
||||
every { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles
|
||||
coEvery { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) } just Runs
|
||||
every { localTogglesStorage.toggles } returns localFeatureToggles
|
||||
every { versionProvider.get() } returns null
|
||||
|
||||
manager.init()
|
||||
|
||||
coVerifyOrder {
|
||||
localFeatureTogglesStorage.init()
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
versionProvider.get()
|
||||
}
|
||||
|
||||
|
|
@ -56,8 +58,8 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
|
||||
@Test
|
||||
fun `failure initialize storage if localFeatureTogglesStorage throws exception`() = runTest {
|
||||
coEvery { localFeatureTogglesStorage.init() } just Runs
|
||||
every { localFeatureTogglesStorage.featureToggles } throws IllegalStateException(
|
||||
coEvery { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) } just Runs
|
||||
every { localTogglesStorage.toggles } throws IllegalStateException(
|
||||
"Property featureToggles should be initialized before get.",
|
||||
)
|
||||
|
||||
|
|
@ -72,14 +74,14 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java)
|
||||
}
|
||||
|
||||
coVerifyOrder { localFeatureTogglesStorage.init() }
|
||||
coVerifyOrder { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) }
|
||||
verifyAll(inverse = true) { versionProvider.get() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failure initialize storage if versionProvider throws exception`() = runTest {
|
||||
coEvery { localFeatureTogglesStorage.init() } just Runs
|
||||
every { localFeatureTogglesStorage.featureToggles } returns localFeatureToggles
|
||||
coEvery { localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH) } just Runs
|
||||
every { localTogglesStorage.toggles } returns localFeatureToggles
|
||||
every { versionProvider.get() } throws PackageManager.NameNotFoundException()
|
||||
|
||||
runCatching { manager.init() }
|
||||
|
|
@ -89,7 +91,7 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
}
|
||||
|
||||
coVerifyOrder {
|
||||
localFeatureTogglesStorage.init()
|
||||
localTogglesStorage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
versionProvider.get()
|
||||
}
|
||||
}
|
||||
|
|
@ -122,8 +124,8 @@ internal class ProdFeatureTogglesManagerTest {
|
|||
|
||||
private companion object {
|
||||
val localFeatureToggles = listOf(
|
||||
FeatureToggle(name = "INACTIVE_TEST_FEATURE_ENABLED", version = "undefined"),
|
||||
FeatureToggle(name = "ACTIVE2_TEST_FEATURE_ENABLED", version = "1.0.0"),
|
||||
ConfigToggle(name = "INACTIVE_TEST_FEATURE_ENABLED", version = "undefined"),
|
||||
ConfigToggle(name = "ACTIVE2_TEST_FEATURE_ENABLED", version = "1.0.0"),
|
||||
)
|
||||
|
||||
val disabledFeatureToggles = mapOf(
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
package com.tangem.core.featuretoggle.storage
|
||||
package com.tangem.core.configtoggle.storage
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.google.common.truth.Truth
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.core.configtoggle.feature.impl.FeatureTogglesConstants
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.asset.reader.AssetReader
|
||||
import io.mockk.*
|
||||
|
|
@ -16,16 +17,16 @@ import java.io.IOException
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
internal class LocalFeatureTogglesStorageTest {
|
||||
internal class LocalTogglesStorageTest {
|
||||
|
||||
private val assetReader = mockk<AssetReader>()
|
||||
private val moshi = mockk<Moshi>()
|
||||
private val jsonAdapter = mockk<JsonAdapter<List<FeatureToggle>>>()
|
||||
private val jsonAdapter = mockk<JsonAdapter<List<ConfigToggle>>>()
|
||||
|
||||
// Impossible to mockk AssetLoader because it implement inline functions
|
||||
private val assetLoader = AssetLoader(assetReader = assetReader, moshi = moshi)
|
||||
|
||||
private val storage = LocalFeatureTogglesStorage(assetLoader)
|
||||
private val storage = LocalTogglesStorage(assetLoader)
|
||||
|
||||
@Test
|
||||
fun `successfully initialize storage`() = runTest {
|
||||
|
|
@ -33,14 +34,14 @@ internal class LocalFeatureTogglesStorageTest {
|
|||
everyCreatingMoshiAdapter() returns jsonAdapter
|
||||
everyMappingJson() returns featureToggles
|
||||
|
||||
storage.init()
|
||||
storage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
|
||||
coVerifyOrder {
|
||||
assetReader.read(CONFIG_FILE_NAME)
|
||||
jsonAdapter.fromJson(json)
|
||||
}
|
||||
|
||||
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(featureToggles)
|
||||
Truth.assertThat(storage.toggles).containsExactlyElementsIn(featureToggles)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -49,33 +50,33 @@ internal class LocalFeatureTogglesStorageTest {
|
|||
everyCreatingMoshiAdapter() returns jsonAdapter
|
||||
everyMappingJson() throws IOException()
|
||||
|
||||
storage.init()
|
||||
storage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
|
||||
coVerifyOrder {
|
||||
assetReader.read(CONFIG_FILE_NAME)
|
||||
jsonAdapter.fromJson(json)
|
||||
}
|
||||
|
||||
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(emptyList<FeatureToggle>())
|
||||
Truth.assertThat(storage.toggles).containsExactlyElementsIn(emptyList<ConfigToggle>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `failure initialize storage if jsonAdapter throws exception`() = runTest {
|
||||
everyReadingJson() throws IOException()
|
||||
|
||||
storage.init()
|
||||
storage.populate(FeatureTogglesConstants.LOCAL_CONFIG_PATH)
|
||||
|
||||
coVerifyOrder { assetReader.read(CONFIG_FILE_NAME) }
|
||||
verifyAll(inverse = true) { jsonAdapter.fromJson(any<String>()) }
|
||||
|
||||
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(emptyList<FeatureToggle>())
|
||||
Truth.assertThat(storage.toggles).containsExactlyElementsIn(emptyList<ConfigToggle>())
|
||||
}
|
||||
|
||||
private fun everyReadingJson() = coEvery { assetReader.read(CONFIG_FILE_NAME) }
|
||||
|
||||
private fun everyCreatingMoshiAdapter() = every {
|
||||
val types = Types.newParameterizedType(List::class.java, FeatureToggle::class.java)
|
||||
moshi.adapter<List<FeatureToggle>>(types)
|
||||
val types = Types.newParameterizedType(List::class.java, ConfigToggle::class.java)
|
||||
moshi.adapter<List<ConfigToggle>>(types)
|
||||
}
|
||||
|
||||
private fun everyMappingJson() = every { jsonAdapter.fromJson(json) }
|
||||
|
|
@ -97,8 +98,8 @@ internal class LocalFeatureTogglesStorageTest {
|
|||
""".trimIndent()
|
||||
|
||||
val featureToggles = listOf(
|
||||
FeatureToggle(name = "INACTIVE_TEST_FEATURE_ENABLED", version = "undefined"),
|
||||
FeatureToggle(name = "ACTIVE2_TEST_FEATURE_ENABLED", version = "1.0.0"),
|
||||
ConfigToggle(name = "INACTIVE_TEST_FEATURE_ENABLED", version = "undefined"),
|
||||
ConfigToggle(name = "ACTIVE2_TEST_FEATURE_ENABLED", version = "1.0.0"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,18 @@ data class OnrampDataResponse(
|
|||
val signature: String,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampDataJson(
|
||||
@Json(name = "widgetUrl")
|
||||
val widgetUrl: String,
|
||||
|
||||
@Json(name = "externalTxId")
|
||||
val externalTxId: String,
|
||||
|
||||
@Json(name = "externalTxUrl")
|
||||
val externalTxUrl: String?,
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class OnrampTxDetails(
|
||||
@Json(name = "fromCurrencyCode")
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ data class OnrampQuoteResponse(
|
|||
val maxFromAmount: String,
|
||||
|
||||
@Json(name = "minToAmount")
|
||||
val minToAmount: String,
|
||||
val minToAmount: String?,
|
||||
|
||||
@Json(name = "maxToAmount")
|
||||
val maxToAmount: String,
|
||||
val maxToAmount: String?,
|
||||
)
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package com.tangem.datasource.local.onramp.paymentmethods
|
||||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OnrampPaymentMethodsStore {
|
||||
|
||||
suspend fun getSyncOrNull(key: String): List<PaymentMethodDTO>?
|
||||
|
||||
fun get(key: String): Flow<List<PaymentMethodDTO>>
|
||||
|
||||
suspend fun store(key: String, value: List<PaymentMethodDTO>)
|
||||
|
||||
suspend fun contains(key: String): Boolean
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ object PreferencesKeys {
|
|||
|
||||
val FEATURE_TOGGLES_KEY by lazy { stringPreferencesKey(name = "featureToggles") }
|
||||
|
||||
val EXCLUDED_BLOCKCHAINS_KEY by lazy { stringSetPreferencesKey(name = "excludedBlockchains") }
|
||||
|
||||
val WAS_TWINS_ONBOARDING_SHOWN by lazy { booleanPreferencesKey(name = "twinsOnboardingShown") }
|
||||
|
||||
val IS_TANGEM_TOS_ACCEPTED_KEY by lazy { booleanPreferencesKey(name = "tangem_tos_accepted") }
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ package com.tangem.core.deeplink.global
|
|||
|
||||
import com.tangem.core.deeplink.DeepLink
|
||||
|
||||
class BuyCurrencyDeepLink(val onReceive: () -> Unit) : DeepLink {
|
||||
class BuyCurrencyDeepLink(val onReceive: (txId: String) -> Unit) : DeepLink {
|
||||
|
||||
override val uri: String = "tangem://redirect?action=dismissBrowser"
|
||||
|
||||
override fun onReceive(params: Map<String, String>) {
|
||||
onReceive()
|
||||
onReceive(
|
||||
params["txId"] ?: return,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.core.featuretoggle.storage
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* Data model with information about feature toggle
|
||||
*
|
||||
* @property name feature toggle name
|
||||
* @property version version number in which the feature will be included
|
||||
*
|
||||
* IMPORTANT: if the version is "undefined", it means that feature toggle is disabled!
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class FeatureToggle(
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "version") val version: String,
|
||||
)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.core.featuretoggle.storage
|
||||
|
||||
/**
|
||||
* Component that initializes and stores a list of feature toggles
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal interface FeatureTogglesStorage {
|
||||
|
||||
/** List of feature toggles */
|
||||
val featureToggles: List<FeatureToggle>
|
||||
|
||||
/** Initialize storage */
|
||||
suspend fun init()
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package com.tangem.core.featuretoggle.storage
|
||||
|
||||
import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage.Companion.LOCAL_CONFIG_PATH
|
||||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
/**
|
||||
* Storage implementation for storing local feature toggles.
|
||||
* Feature toggles are declared in file [LOCAL_CONFIG_PATH].
|
||||
*
|
||||
* @property assetLoader asset loader
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class LocalFeatureTogglesStorage(
|
||||
private val assetLoader: AssetLoader,
|
||||
) : FeatureTogglesStorage {
|
||||
|
||||
override var featureToggles: List<FeatureToggle> by Delegates.notNull()
|
||||
private set
|
||||
|
||||
override suspend fun init() {
|
||||
featureToggles = assetLoader.loadList<FeatureToggle>(LOCAL_CONFIG_PATH)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val LOCAL_CONFIG_PATH: String = "configs/feature_toggles_config"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.core.featuretoggle.utils
|
||||
|
||||
import com.tangem.core.featuretoggle.contract.VersionAvailabilityContract
|
||||
import com.tangem.core.featuretoggle.storage.FeatureToggle
|
||||
|
||||
internal fun List<FeatureToggle>.associateToggles(currentVersion: String): Map<String, Boolean> {
|
||||
return associate { localToggle ->
|
||||
Pair(
|
||||
first = localToggle.name,
|
||||
second = VersionAvailabilityContract(currentVersion, localToggle.version),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="action_buttons_buy_empty_search_message">Token nicht in Deinem Portfolio gefunden? Überprüfe die Märkte, um es zu finden und zum Kauf hinzuzufügen</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Token nicht in Deinem Portfolio gefunden? Überprüfe die Märkte, um es zu finden und zum Verkauf hinzuzufügen</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Token nicht in Ihrem Portfolio gefunden? Überprüfe die Märkte, um es zu finden und zum Verkauf hinzuzufügen</string>
|
||||
<string name="action_buttons_swap_choose_token">Wähle den Token</string>
|
||||
<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="action_buttons_you_want_to_receive">Wähle den Token aus, das Du erhalten möchtest</string>
|
||||
<string name="action_buttons_you_want_to_swap">Wähle den Token, den Du tauschen möchtest</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>
|
||||
|
|
@ -126,7 +126,7 @@
|
|||
<string name="common_fee_selector_option_market">Markt</string>
|
||||
<string name="common_fee_selector_option_slow">Langsam</string>
|
||||
<string name="common_fee_selector_title">Gebühren</string>
|
||||
<string name="common_generate_addresses">Adressen abrufen</string>
|
||||
<string name="common_generate_addresses">Adressen synchronisieren</string>
|
||||
<string name="common_go_to_provider">Zum Anbieter gehen</string>
|
||||
<string name="common_go_to_token">Zum Token</string>
|
||||
<string name="common_hour">stunde</string>
|
||||
|
|
@ -251,6 +251,7 @@
|
|||
<string name="express_choose_providers_subtitle">Tangem bietet Token-Swaps über Drittanbieter gemäß den jeweiligen Bedingungen des jeweiligen Anbieters an.</string>
|
||||
<string name="express_choose_providers_title">Anbieter wählen</string>
|
||||
<string name="express_error_code">Es ist ein Fehler aufgetreten. Code: %s</string>
|
||||
<string name="express_error_provider_amount_roundup">Fehler %1$s. Der ausgewählte Anbieter kann die angegebene Transaktion nicht verarbeiten. Bitte runde den Wert auf %2$s auf oder änder diesen.</string>
|
||||
<string name="express_error_swap_pair_unavailable">Der gewählte Anbieter ist im Moment nicht verfügbar. Bitte versuche es später noch einmal. (Code: %s)</string>
|
||||
<string name="express_error_swap_unavailable">Swaps sind im Moment nicht verfügbar. Bitte versuche es später noch einmal. (Code: %s)</string>
|
||||
<string name="express_estimated_amount">Geschätzter Betrag</string>
|
||||
|
|
@ -296,7 +297,7 @@
|
|||
<string name="express_provider_recommended">Empfohlen</string>
|
||||
<string name="express_status_bought">Gekauft %s</string>
|
||||
<string name="express_status_buying">Kauf %s</string>
|
||||
<string name="express_status_buying_active">Kauf %s...</string>
|
||||
<string name="express_status_buying_active">Kauf %s…</string>
|
||||
<string name="express_token_list_empty_search">Keine Token gefunden. Bitte versuche eine andere Anfrage</string>
|
||||
<string name="express_transaction_id">ID: %s</string>
|
||||
<string name="express_transaction_id_copied">Transaktions-ID kopiert</string>
|
||||
|
|
@ -565,6 +566,8 @@
|
|||
<string name="onboarding_wallet_info_title_fourth">Zugangscode wiederherstellen</string>
|
||||
<string name="onboarding_wallet_info_title_second">Identische Karten oder Ring</string>
|
||||
<string name="onboarding_wallet_info_title_third">Zugangscode</string>
|
||||
<string name="onramp_avaiable_with_payment_methods">Verfügbar mit %s</string>
|
||||
<string name="onramp_choose_provider_title_hint">Anbieter erleichtern Transaktionen und sorgen für einen reibungslosen und effizienten Token-Austausch</string>
|
||||
<string name="onramp_country_search">Suche nach Land</string>
|
||||
<string name="onramp_country_unavailable">Nicht verfügbar</string>
|
||||
<string name="onramp_currency_search">Suche nach Währung</string>
|
||||
|
|
@ -572,10 +575,11 @@
|
|||
<string name="onramp_min_amount_restriction">Der zu kaufende Betrag muss mindestens %s betragen</string>
|
||||
<string name="onramp_no_available_providers">Keine verfügbaren Anbieter für diese Währung</string>
|
||||
<string name="onramp_pay_with">Bezahlen mit</string>
|
||||
<string name="onramp_provider_max_amount">Verfügbar bis zu %s</string>
|
||||
<string name="onramp_provider_min_amount">Erhältlich bei %s</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">Du kannst Deine Transaktion beim Drittanbieter %s abschließen.</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Umleitung auf %s…</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">Unsere Dienstleistungen sind in diesem Land nicht verfügbar</string>
|
||||
<string name="onramp_residency_bottomsheet_country_subtitle">Änder oder bestätige bitte</string>
|
||||
<string name="onramp_residency_bottomsheet_title">Dein Wohnsitz wurde identifiziert als</string>
|
||||
<string name="onramp_settings_residence">Residenz</string>
|
||||
<string name="onramp_settings_residence_description">Bitte wähle das richtige Land aus, um korrekte Zahlungsoptionen und Dienstleistungen zu gewährleisten.</string>
|
||||
|
|
@ -590,8 +594,11 @@
|
|||
<string name="qr_scanner_camera_denied_settings_button">Einstellungen</string>
|
||||
<string name="qr_scanner_camera_denied_text">Du hast keinen Zugriff auf deine Kamera gewährt</string>
|
||||
<string name="qr_scanner_camera_denied_title">Kamerazugriff verweigert</string>
|
||||
<string name="receive_bottom_sheet_no_memo_required_message">Kein Memo erforderlich</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s ( %2$s ) im %3$s Netzwerk</string>
|
||||
<string name="receive_bottom_sheet_warning_message_description">Das Senden einer anderen Währung führt zu deren irreversiblem Verlust.</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">Sende nur %s an diese Adresse. Der Versand einer anderen Währung führt zu ihrem unwiderruflichen Verlust.</string>
|
||||
<string name="receive_bottom_sheet_warning_message_title">Sende nur %s an diese Adresse.</string>
|
||||
<string name="receive_token_description">Überweise Geld von einem anderen Wallet oder einer anderen Börse</string>
|
||||
<string name="referral_button_participate">Teilnehmen</string>
|
||||
<string name="referral_error_failed_to_load_info">Die Informationen zum Empfehlungsprogramm konnten nicht geladen werden. Bitte versuche es später noch einmal.</string>
|
||||
|
|
@ -1004,12 +1011,12 @@
|
|||
<string name="warning_failed_to_verify_card_message">Bei dieser Karte oder Ring kann es sich um ein Produktionsmuster oder eine Fälschung handeln</string>
|
||||
<string name="warning_failed_to_verify_card_title">Echtheitsprüfung fehlgeschlagen</string>
|
||||
<string name="warning_hedera_missing_token_association_button_title">zuordnen</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Dieser Token muss mit Ihrem Hedera-Konto verbunden sein, bevor du diesen erhalten kannst. Zuordnungsgebühr ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Dieser Token muss mit Ihrem Hedera-Konto verbunden sein, bevor du diesen erhalten kannst. Zuordnungsgebühr ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message_brief">Dieses Token muss mit deinem Hedera-Konto verknüpft sein, bevor du ihn erhalten kannst.</string>
|
||||
<string name="warning_hedera_missing_token_association_title">Verknüpfe deinen Token</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">Nicht genug %s. Lade dein Hedera-Konto auf, um dieses Token zuzuordnen</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Bist Du sicher, dass Du die Transaktion stornieren willst? Du kannst die Transaktion nicht erneut starten.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Deine Transaktion mit einem Betrag von %1$s %2$s wurde nicht abgeschlossen. Du kannst versuchen, diese erneut abzuschließen.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Deine Transaktion mit einem Betrag von %1$s %2$s wurde nicht abgeschlossen. Du kannst versuchen, diese erneut abzuschließen.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_title">Du hast eine nicht abgeschlossene Transaktion</string>
|
||||
<string name="warning_low_signatures_message">Auf dieser Karte oder Ring sind nur noch %s Unterschriften übrig. Du musst dein gesamtes Guthaben abheben.</string>
|
||||
<string name="warning_low_signatures_title">Geringe Anzahl von Unterschriften</string>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
<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>
|
||||
|
|
@ -126,7 +124,7 @@
|
|||
<string name="common_fee_selector_option_market">Mercado</string>
|
||||
<string name="common_fee_selector_option_slow">Lento</string>
|
||||
<string name="common_fee_selector_title">Velocidad y tarifa</string>
|
||||
<string name="common_generate_addresses">Obtener direcciones</string>
|
||||
<string name="common_generate_addresses">Sincronizar direcciones</string>
|
||||
<string name="common_go_to_provider">Ir al proveedor</string>
|
||||
<string name="common_go_to_token">Ir al token</string>
|
||||
<string name="common_hour">hora</string>
|
||||
|
|
@ -432,7 +430,6 @@
|
|||
<string name="markets_token_details_fully_diluted_valuation_description">El valor teórico total de una criptomoneda si todas las monedas que podrían existir estuvieran en circulación, incluidas las que actualmente no circulan.</string>
|
||||
<string name="markets_token_details_fully_diluted_valuation_full">Valoración totalmente diluida</string>
|
||||
<string name="markets_token_details_genesis_date">Dato de Genesis</string>
|
||||
<string name="markets_token_details_genesis_date_description">Vacío</string>
|
||||
<string name="markets_token_details_high">Alto</string>
|
||||
<string name="markets_token_details_holders">Titulares</string>
|
||||
<string name="markets_token_details_holders_description">El cambio en el número de poseedores de tokens dentro de un período de tiempo específico</string>
|
||||
|
|
@ -443,7 +440,6 @@
|
|||
<string name="markets_token_details_liquidity_description">El cambio en la cantidad de liquidez disponible para el token durante el período de tiempo especificado</string>
|
||||
<string name="markets_token_details_liquidity_full">Liquidez</string>
|
||||
<string name="markets_token_details_liquidity_index">Índice de liquidez</string>
|
||||
<string name="markets_token_details_liquidity_index_description">Vacío</string>
|
||||
<string name="markets_token_details_listed_on">Listado en</string>
|
||||
<string name="markets_token_details_low">Bajo</string>
|
||||
<string name="markets_token_details_market_capitalization">Capital. de mercado</string>
|
||||
|
|
@ -517,12 +513,12 @@
|
|||
<string name="onboarding_navbar_title_creating_backup">Creando un backup</string>
|
||||
<string name="onboarding_seed_button_read_more">Leer más sobre seed phrase</string>
|
||||
<plurals name="onboarding_seed_generate_message_words_count">
|
||||
<item quantity="one">Vacío</item>
|
||||
<item quantity="one"></item>
|
||||
<item quantity="other">Escriba estas %d palabras en el orden que se indica a continuación y guárdelas en un lugar seguro y secreto.</item>
|
||||
</plurals>
|
||||
<string name="onboarding_seed_generate_title">Su seed phrase</string>
|
||||
<plurals name="onboarding_seed_generate_words_count">
|
||||
<item quantity="one">Vacío</item>
|
||||
<item quantity="one"></item>
|
||||
<item quantity="other">%d palabras</item>
|
||||
</plurals>
|
||||
<string name="onboarding_seed_import_message">Para importar su billetera, ingrese su seed phrase en el campo de abajo</string>
|
||||
|
|
@ -574,10 +570,11 @@
|
|||
<string name="onramp_min_amount_restriction">La cantidad a comprar debe ser como mínimo %s</string>
|
||||
<string name="onramp_no_available_providers">No hay proveedores disponibles para esta moneda</string>
|
||||
<string name="onramp_pay_with">Pagar con</string>
|
||||
<string name="onramp_provider_max_amount">Disponible hasta %s</string>
|
||||
<string name="onramp_provider_min_amount">Disponible desde %s</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">Podrá completar su transacción en el proveedor externo, %s</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Redirigiendo a %s...</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">Nuestros servicios no están disponibles en este país</string>
|
||||
<string name="onramp_residency_bottomsheet_country_subtitle">Cámbielo o confírmelo</string>
|
||||
<string name="onramp_residency_bottomsheet_title">Su residencia ha sido identificada como</string>
|
||||
<string name="onramp_settings_residence">Residencia</string>
|
||||
<string name="onramp_settings_residence_description">Seleccione el país correcto para garantizar opciones de pago y servicios precisos.</string>
|
||||
|
|
@ -1006,12 +1003,12 @@
|
|||
<string name="warning_failed_to_verify_card_message">Esta tarjeta podría ser una muestra de producción o una falsificación</string>
|
||||
<string name="warning_failed_to_verify_card_title">La verificación de autenticidad falló</string>
|
||||
<string name="warning_hedera_missing_token_association_button_title">Asociar</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Este token debe estar asociado a tu cuenta de Hedera antes de que pueda recibirlo. Tarifa de asociación ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Este token debe estar asociado a tu cuenta de Hedera antes de que pueda recibirlo. Tarifa de asociación ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message_brief">Este token debe estar asociado con su cuenta de Hedera antes de poder recibirlo.</string>
|
||||
<string name="warning_hedera_missing_token_association_title">Asocie su token</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">No hay suficiente %s. Recargue su cuenta de Hedera para asociar este token</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_discard_message">¿Está seguro(a) de que desea cancelar la transacción? No podrá volver a intentar realizarla.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Su transacción con un importe de %1$s %2$s no se ha completado. Puede volver a intentarlo para completarla.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Su transacción con un importe de %1$s %2$s no se ha completado. Puede volver a intentarlo para completarla.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_title">Tiene una transacción sin terminar</string>
|
||||
<string name="warning_low_signatures_message">Solo quedan %s firmas en esta tarjeta. Deba retirar todos sus fondos.</string>
|
||||
<string name="warning_low_signatures_title">Recuento de firmas bajo</string>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
<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>
|
||||
|
|
@ -126,7 +124,7 @@
|
|||
<string name="common_fee_selector_option_market">Marché</string>
|
||||
<string name="common_fee_selector_option_slow">Lent</string>
|
||||
<string name="common_fee_selector_title">Vitesse et frais</string>
|
||||
<string name="common_generate_addresses">Obtenir des adresses</string>
|
||||
<string name="common_generate_addresses">Synchroniser les adresses</string>
|
||||
<string name="common_go_to_provider">Aller au fournisseur</string>
|
||||
<string name="common_go_to_token">Aller au jeton</string>
|
||||
<string name="common_hour">heure</string>
|
||||
|
|
@ -370,7 +368,7 @@
|
|||
<string name="manage_tokens_wallet_selector_title">Choisissez un portefeuille</string>
|
||||
<string name="manage_tokens_wallet_support_only_one_network_title">Le portefeuille ne prend pas en charge plus d\'un réseau</string>
|
||||
<string name="markets_add_to_my_portfolio_description">Pour acheter, échanger ou recevoir cet actif, ajoutez-le à votre portefeuille</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_description">Cet actif n\'est plus pris en charge dans le portefeuille</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_description">Cet actif n\'est pas pris en charge dans le portefeuille</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_for_wallet_description">Cet actif n\'est pas disponible pour ce portefeuille</string>
|
||||
<string name="markets_add_to_portfolio_button">Ajouter au portfolio</string>
|
||||
<string name="markets_add_token">Ajoutez un jeton</string>
|
||||
|
|
@ -432,7 +430,6 @@
|
|||
<string name="markets_token_details_fully_diluted_valuation_description">La valeur théorique totale d\'une crypto-monnaie si toutes les pièces qui pourraient exister étaient en circulation, y compris celles qui ne circulent pas actuellement</string>
|
||||
<string name="markets_token_details_fully_diluted_valuation_full">Valorisation entièrement diluée</string>
|
||||
<string name="markets_token_details_genesis_date">Date de la Genesis</string>
|
||||
<string name="markets_token_details_genesis_date_description">Vide</string>
|
||||
<string name="markets_token_details_high">Haut</string>
|
||||
<string name="markets_token_details_holders">Détenteurs</string>
|
||||
<string name="markets_token_details_holders_description">L\'évolution du nombre de détenteurs de jetons au cours d\'une période donnée</string>
|
||||
|
|
@ -443,7 +440,6 @@
|
|||
<string name="markets_token_details_liquidity_description">Le changement dans la quantité de liquidité disponible pour le jeton pendant la période spécifiée</string>
|
||||
<string name="markets_token_details_liquidity_full">Liquidité</string>
|
||||
<string name="markets_token_details_liquidity_index">Indice de liquidité</string>
|
||||
<string name="markets_token_details_liquidity_index_description">Vide</string>
|
||||
<string name="markets_token_details_listed_on">Listé sur</string>
|
||||
<string name="markets_token_details_low">Faible</string>
|
||||
<string name="markets_token_details_market_capitalization">Cap. boursière</string>
|
||||
|
|
@ -517,12 +513,12 @@
|
|||
<string name="onboarding_navbar_title_creating_backup">Sauvegarde en cours</string>
|
||||
<string name="onboarding_seed_button_read_more">En savoir plus sur les seed phrases</string>
|
||||
<plurals name="onboarding_seed_generate_message_words_count">
|
||||
<item quantity="one">Vide</item>
|
||||
<item quantity="one"></item>
|
||||
<item quantity="other">Écrivez ces %d mots dans l\'ordre indiqué ci-dessous et conservez-les dans un endroit sûr et secret.</item>
|
||||
</plurals>
|
||||
<string name="onboarding_seed_generate_title">Votre seed phrase</string>
|
||||
<plurals name="onboarding_seed_generate_words_count">
|
||||
<item quantity="one">Vide</item>
|
||||
<item quantity="one"></item>
|
||||
<item quantity="other">%d mots</item>
|
||||
</plurals>
|
||||
<string name="onboarding_seed_import_message">Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous</string>
|
||||
|
|
@ -574,10 +570,11 @@
|
|||
<string name="onramp_min_amount_restriction">Le montant à acheter doit être au moins %s</string>
|
||||
<string name="onramp_no_available_providers">Aucun fournisseur disponible pour cette devise</string>
|
||||
<string name="onramp_pay_with">Payer avec</string>
|
||||
<string name="onramp_provider_max_amount">Disponible jusqu\'à %s</string>
|
||||
<string name="onramp_provider_min_amount">Disponible à partir de %s</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">Vous pourrez finaliser votre transaction sur le fournisseur tiers, %s</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Redirection vers %s...</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">Nos services ne sont pas disponibles dans ce pays</string>
|
||||
<string name="onramp_residency_bottomsheet_country_subtitle">Modifiez-le ou confirmez-le</string>
|
||||
<string name="onramp_residency_bottomsheet_title">Votre résidence a été identifiée comme</string>
|
||||
<string name="onramp_settings_residence">Résidence</string>
|
||||
<string name="onramp_settings_residence_description">Veuillez sélectionner le bon pays pour garantir des options de paiement et des services précis.</string>
|
||||
|
|
@ -607,9 +604,9 @@
|
|||
<item quantity="one">pour le portefeuille %d</item>
|
||||
<item quantity="other">pour les portefeuilles %d </item>
|
||||
</plurals>
|
||||
<string name="referral_point_currencies_description">Obtiendras ^^%1$s^^ pour chaque portefeuille acheté par votre ami sur votre adresse de réseau %2$s %3$s ^^30 jours après^^</string>
|
||||
<string name="referral_point_currencies_description">Obtiendrez ^^%1$s^^ pour chaque portefeuille acheté par votre ami sur votre adresse de réseau %2$s %3$s ^^30 jours après^^</string>
|
||||
<string name="referral_point_currencies_title">Vous</string>
|
||||
<string name="referral_point_discount_description_prefix">Obtiendrez</string>
|
||||
<string name="referral_point_discount_description_prefix">Obtiendra</string>
|
||||
<string name="referral_point_discount_description_suffix">lors de l\'achat d\'un portefeuille sur tangem.com</string>
|
||||
<string name="referral_point_discount_description_value">%s réduction</string>
|
||||
<string name="referral_point_discount_title">Votre ami</string>
|
||||
|
|
@ -1006,12 +1003,12 @@
|
|||
<string name="warning_failed_to_verify_card_message">Cette carte pourrait être un échantillon de production ou une contrefaçon</string>
|
||||
<string name="warning_failed_to_verify_card_title">Échec de la vérification d\'authenticité</string>
|
||||
<string name="warning_hedera_missing_token_association_button_title">Associer</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Ce jeton doit être associé à votre compte Hedera avant que vous puissiez le recevoir. Frais d\'association ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Ce jeton doit être associé à votre compte Hedera avant que vous puissiez le recevoir. Frais d\'association ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message_brief">Ce jeton doit être associé à votre compte Hedera avant que vous puissiez le recevoir</string>
|
||||
<string name="warning_hedera_missing_token_association_title">Associez votre jeton</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">%s insuffisant. Renflouez votre compte Hedera pour associer ce jeton</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Êtes-vous sûr(e) de vouloir annuler la transaction ? Vous ne pourrez plus réessayer.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Votre transaction d\'un montant de %1$s %2$s n\'a pas été finalisée. Vous pouvez réessayer plus tard pour la finaliser.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Votre transaction d\'un montant de %1$s %2$s n\'a pas été finalisée. Vous pouvez réessayer plus tard pour la finaliser.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_title">Vous avez une transaction inachevée</string>
|
||||
<string name="warning_low_signatures_message">Il ne reste que %s signatures sur cette carte. Vous devez retirer tous vos fonds.</string>
|
||||
<string name="warning_low_signatures_title">Faible nombre de signatures</string>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
<resources>
|
||||
<string name="action_buttons_buy_empty_search_message">ポートフォリオにトークンが見つかりませんか?マーケットから見つけ、買付のために追加してください</string>
|
||||
<string name="action_buttons_sell_empty_search_message">ポートフォリオにトークンが見つかりませんか?マーケットから見つけ、売却のために追加してください</string>
|
||||
<string name="action_buttons_sell_navigation_bar_title">暗号資産を売却</string>
|
||||
<string name="action_buttons_swap_choose_token">トークンを選択</string>
|
||||
<string name="action_buttons_swap_empty_search_message">ポートフォリオにトークンが見つかりませんか?マーケットから見つけ、スワップのために追加してください</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">選択したトークンと交換できるトークンがありません。別のトークンを選択してください。</string>
|
||||
<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="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>
|
||||
|
|
@ -123,7 +124,7 @@
|
|||
<string name="common_fee_selector_option_market">マーケット</string>
|
||||
<string name="common_fee_selector_option_slow">遅い</string>
|
||||
<string name="common_fee_selector_title">速度と料金</string>
|
||||
<string name="common_generate_addresses">アドレスを取得する</string>
|
||||
<string name="common_generate_addresses">アドレスを同期する</string>
|
||||
<string name="common_go_to_provider">プロバイダーへ移動</string>
|
||||
<string name="common_go_to_token">トークンへ移動</string>
|
||||
<string name="common_hour">時間</string>
|
||||
|
|
@ -248,6 +249,7 @@
|
|||
<string name="express_choose_providers_subtitle">Tangemは、各プロバイダーの条件に従って、サードパーティプロバイダーを介してトークンスワップを提供します。</string>
|
||||
<string name="express_choose_providers_title">プロバイダーを選択</string>
|
||||
<string name="express_error_code">エラーが発生しました。コード: %s</string>
|
||||
<string name="express_error_provider_amount_roundup">エラー%1$s 。選択したプロバイダーは指定された取引を処理できません。値を%2$sに切り上げるか、変更してください。</string>
|
||||
<string name="express_error_swap_pair_unavailable">選択したプロバイダーは現在利用できません。しばらくしてからもう一度お試しください。(コード: %s )</string>
|
||||
<string name="express_error_swap_unavailable">現在、交換はご利用いただけません。しばらくしてからもう一度お試しください。(コード: %s )</string>
|
||||
<string name="express_estimated_amount">推定金額</string>
|
||||
|
|
@ -557,6 +559,8 @@
|
|||
<string name="onboarding_wallet_info_title_fourth">アクセスコードの復元</string>
|
||||
<string name="onboarding_wallet_info_title_second">同一のカード</string>
|
||||
<string name="onboarding_wallet_info_title_third">アクセスコード</string>
|
||||
<string name="onramp_avaiable_with_payment_methods">%sで利用可能</string>
|
||||
<string name="onramp_choose_provider_title_hint">プロバイダーは取引を促進し、スムーズで効率的なトークン交換を保証します</string>
|
||||
<string name="onramp_country_search">国で検索</string>
|
||||
<string name="onramp_country_unavailable">利用不可</string>
|
||||
<string name="onramp_currency_search">通貨で検索</string>
|
||||
|
|
@ -564,10 +568,11 @@
|
|||
<string name="onramp_min_amount_restriction">買付金額は少なくとも%sである必要があります</string>
|
||||
<string name="onramp_no_available_providers">この通貨で利用可能なプロバイダーはありません</string>
|
||||
<string name="onramp_pay_with">支払う</string>
|
||||
<string name="onramp_provider_max_amount">最大 %s まで使用可能</string>
|
||||
<string name="onramp_provider_min_amount">%s 以上で利用可能</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">サードパーティプロバイダー%sで取引を完了できます。</string>
|
||||
<string name="onramp_redirecting_to_provider_title">%sにリダイレクトしています...</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">この国では当社のサービスはご利用いただけません</string>
|
||||
<string name="onramp_residency_bottomsheet_country_subtitle">変更または確認</string>
|
||||
<string name="onramp_residency_bottomsheet_title">あなたの住居は次のように識別されています</string>
|
||||
<string name="onramp_settings_residence">住居</string>
|
||||
<string name="onramp_settings_residence_description">正確なお支払い方法とサービスを確保するため、正しい国を選択してください。</string>
|
||||
|
|
@ -582,8 +587,11 @@
|
|||
<string name="qr_scanner_camera_denied_settings_button">設定</string>
|
||||
<string name="qr_scanner_camera_denied_text">カメラへのアクセスを許可していません。</string>
|
||||
<string name="qr_scanner_camera_denied_title">カメラへのアクセスが拒否されました</string>
|
||||
<string name="receive_bottom_sheet_no_memo_required_message">メモ不要</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%3$sネットワーク上の%1$s ( %2$s )</string>
|
||||
<string name="receive_bottom_sheet_warning_message_description">他の暗号資産を送信すると、取り返しのつかない損失が発生します。</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">このアドレスには%sのみを送金してください。他のトークンを送信すると、取り返しのつかない損失が発生します。</string>
|
||||
<string name="receive_bottom_sheet_warning_message_title">このアドレスには%sのみを送信してください。</string>
|
||||
<string name="receive_token_description">ウォレットや取引所から資金を送金する</string>
|
||||
<string name="referral_button_participate">参加する</string>
|
||||
<string name="referral_error_failed_to_load_info">紹介プログラムに関する情報を読み込めませんでした。しばらくしてからもう一度お試しください。</string>
|
||||
|
|
@ -994,12 +1002,12 @@
|
|||
<string name="warning_failed_to_verify_card_message">このカードは、サンプル品または偽造品である可能性があります</string>
|
||||
<string name="warning_failed_to_verify_card_title">真正性チェックに失敗しました</string>
|
||||
<string name="warning_hedera_missing_token_association_button_title">関連付ける</string>
|
||||
<string name="warning_hedera_missing_token_association_message">このトークンを受け取るには、Hederaアカウントに関連付ける必要があります。関連付け手数料 ~ %1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message">このトークンを受け取るには、Hederaアカウントに関連付ける必要があります。関連付け手数料 ~ %1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message_brief">このトークンを受け取るには、Hederaアカウントに関連付ける必要があります。</string>
|
||||
<string name="warning_hedera_missing_token_association_title">トークンを関連付ける</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">%sが不足しています。このトークンを関連付けるには、Hederaアカウントに資金を追加してください。</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_discard_message">取引をキャンセルしてもよろしいですか? 取引を再試行することはできません</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">%1$s %2$sの取引が完了しませんでした。もう一度試して完了してください。</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">%1$s %2$sの取引が完了しませんでした。もう一度試して完了してください。</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_title">未完了の取引があります</string>
|
||||
<string name="warning_low_signatures_message">このカードには%sの署名のみが残っています。資金をすべて引き出す必要があります。</string>
|
||||
<string name="warning_low_signatures_title">署名数が少ないです</string>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="action_buttons_buy_empty_search_message">Не нашли свой токен? Перейдите в раздел «Рынок» на главной странице и добавьте его в свой портфель для покупки.</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Не нашли свой токен? Перейдите в раздел «Рынок» на главной странице и добавьте его в свой портфель для продажи.</string>
|
||||
<string name="action_buttons_sell_navigation_bar_title">Продать криптовалюту</string>
|
||||
<string name="action_buttons_swap_choose_token">Выберите токен</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">Нет доступных токенов для обмена с выбранным токеном, пожалуйста, выберите другой.</string>
|
||||
<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="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>
|
||||
|
|
@ -129,7 +133,7 @@
|
|||
<string name="common_fee_selector_option_market">По рынку</string>
|
||||
<string name="common_fee_selector_option_slow">Медленно</string>
|
||||
<string name="common_fee_selector_title">Скорость и комиссия</string>
|
||||
<string name="common_generate_addresses">Получить ваши адреса</string>
|
||||
<string name="common_generate_addresses">Синхронизировать адреса</string>
|
||||
<string name="common_go_to_provider">К провайдеру</string>
|
||||
<string name="common_go_to_token">Перейти в токен</string>
|
||||
<string name="common_hour">час</string>
|
||||
|
|
@ -254,6 +258,7 @@
|
|||
<string name="express_choose_providers_subtitle">Tangem предоставляет доступ к обмену через сторонних поставщиков в соответствии с их правилами</string>
|
||||
<string name="express_choose_providers_title">Выберите провайдера</string>
|
||||
<string name="express_error_code">Произошла ошибка. Код: %s</string>
|
||||
<string name="express_error_provider_amount_roundup">Ошибка %1$s. Выбранный провайдер не может обработать указанную транзакцию. Округлите значение до %2$s или измените его.</string>
|
||||
<string name="express_error_swap_pair_unavailable">Выбранный провайдер недоступен для обмена. Попробуйте позже. (Код: %s)</string>
|
||||
<string name="express_error_swap_unavailable">В данный момент обмен невозможен. Попробуйте позже. (Код: %s)</string>
|
||||
<string name="express_estimated_amount">Курс обмена</string>
|
||||
|
|
@ -297,6 +302,9 @@
|
|||
<string name="express_provider_not_available">Недоступно для этой пары</string>
|
||||
<string name="express_provider_permission_needed">Требуется разрешение</string>
|
||||
<string name="express_provider_recommended">Рекомендовано</string>
|
||||
<string name="express_status_bought">Куплено %s</string>
|
||||
<string name="express_status_buying">Покупка %s</string>
|
||||
<string name="express_status_buying_active">Покупка %s...</string>
|
||||
<string name="express_token_list_empty_search">Токены не найдены. Пожалуйста, попробуйте другой запрос</string>
|
||||
<string name="express_transaction_id">ID: %s</string>
|
||||
<string name="express_transaction_id_copied">ID транзакции скопирован</string>
|
||||
|
|
@ -573,17 +581,25 @@
|
|||
<string name="onboarding_wallet_info_title_fourth">Восстановление кода доступа</string>
|
||||
<string name="onboarding_wallet_info_title_second">Идентичные карты</string>
|
||||
<string name="onboarding_wallet_info_title_third">Код доступа</string>
|
||||
<string name="onramp_avaiable_with_payment_methods">Доступно с %s</string>
|
||||
<string name="onramp_choose_provider_title_hint">Tangem предоставляет доступ к покупке через сторонних провайдеров в соответствии с их правилами</string>
|
||||
<string name="onramp_country_search">Поиск по стране</string>
|
||||
<string name="onramp_country_unavailable">Недоступно</string>
|
||||
<string name="onramp_currency_search">Поиск по валюте</string>
|
||||
<string name="onramp_max_amount_restriction">Сумма покупки не может быть больше, чем %s</string>
|
||||
<string name="onramp_min_amount_restriction">Сумма покупки должна составлять минимум %s</string>
|
||||
<string name="onramp_no_available_providers">Для данной валюты нет доступных провайдеров</string>
|
||||
<string name="onramp_no_available_providers">Нет доступных провайдеров для выбранной валюты</string>
|
||||
<string name="onramp_pay_with">Оплата с</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">Вы сможете завершить транзакцию через сервис стороннего провайдера, %s</string>
|
||||
<string name="onramp_provider_max_amount">Доступно до %s</string>
|
||||
<string name="onramp_provider_min_amount">Доступно с %s</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">Вы сможете продолжить транзакцию через сервис стороннего провайдера, %s</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Переход к %s...</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">Наши сервисы недоступны в данной стране</string>
|
||||
<string name="onramp_residency_bottomsheet_title">Ваше местоположение определено как</string>
|
||||
<string name="onramp_settings_residence">Страна проживания</string>
|
||||
<string name="onramp_settings_residence_description">Пожалуйста, укажите правильную страну, чтобы обеспечить точность доступных способов оплаты и услуг.</string>
|
||||
<string name="onramp_settings_title">Настройки</string>
|
||||
<string name="onramp_transaction_status_footer_text">Вы можете закрыть этот экран и проверить статус транзакции на экране информации о токене.</string>
|
||||
<string name="onramp_via">Через</string>
|
||||
<string name="organize_tokens_group">Группы</string>
|
||||
<string name="organize_tokens_sort_by_balance">По балансу</string>
|
||||
|
|
@ -593,8 +609,11 @@
|
|||
<string name="qr_scanner_camera_denied_settings_button">Настройки</string>
|
||||
<string name="qr_scanner_camera_denied_text">Вы не предоставили доступ к вашей камере</string>
|
||||
<string name="qr_scanner_camera_denied_title">Доступ к камере запрещен</string>
|
||||
<string name="receive_bottom_sheet_no_memo_required_message">Memo не требуется</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) в сети %3$s</string>
|
||||
<string name="receive_bottom_sheet_warning_message_description">Использование другой сети может привести к утрате средств.</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств.</string>
|
||||
<string name="receive_bottom_sheet_warning_message_title">Отправляйте только %s на этот адрес</string>
|
||||
<string name="receive_token_description">Переводите средства с любого кошелька или биржи</string>
|
||||
<string name="referral_button_participate">Участвовать</string>
|
||||
<string name="referral_error_failed_to_load_info">Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.</string>
|
||||
|
|
@ -650,6 +669,10 @@
|
|||
<string name="scan_card_settings_button">Сканировать</string>
|
||||
<string name="scan_card_settings_message">Отсканируйте карту или кольцо, чтобы изменить ее настройки. Изменения затронут только ту карту или кольцо, которые вы отсканировали, и не повлияют на другие устройства, привязанные к вашему кошельку.</string>
|
||||
<string name="scan_card_settings_title">Приготовьте свой Tangem!</string>
|
||||
<string name="selling_insufficient_balance_alert_message">На вашем балансе недостаточно средств для продажи этой криптовалюты. Пожалуйста, пополните нужный актив, чтобы продолжить.</string>
|
||||
<string name="selling_insufficient_balance_alert_title">Недостаточно средств</string>
|
||||
<string name="selling_regional_restriction_alert_message">Продажа криптовалюты в вашем регионе временно недоступна. Мы активно работаем над тем, чтобы добавить эту возможность. Следите за нашими новостями!</string>
|
||||
<string name="selling_regional_restriction_alert_title">Региональные ограничения</string>
|
||||
<string name="send_additional_field_already_included">Уже содержится во введенном адресе</string>
|
||||
<string name="send_alert_fee_too_high_text">Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна.</string>
|
||||
<string name="send_alert_fee_too_low_text">Вы указали комиссию ниже рекомендуемой, это может привести к задержке исполнения вашей транзакции. Продолжить?</string>
|
||||
|
|
@ -690,6 +713,7 @@
|
|||
<string name="send_notification_fee_too_high_title">Установлена высокая комиссия</string>
|
||||
<string name="send_notification_high_fee_text">Ввиду особенности сети %1$s комиссия при переводе всего баланса выше. Для того, чтобы снизить комиссию Вы можете оставить %2$s.</string>
|
||||
<string name="send_notification_high_fee_title">Комиссия повышена</string>
|
||||
<string name="send_notification_invalid_amount_rent_fee">Баланс вашего счета не может быть ниже арендной платы. Пожалуйста, оставьте на счете не менее %1$s или выведите все средства.</string>
|
||||
<string name="send_notification_invalid_amount_text">Включенная комиссия превышает сумму перевода, что приводит к отрицательному значению</string>
|
||||
<string name="send_notification_invalid_amount_title">Недопустимая сумма</string>
|
||||
<string name="send_notification_invalid_minimum_amount_text">Минимальная сумма отправки - %1$s. Пожалуйста, убедитесь, что остаток после отправки также не будет меньше %2$s.</string>
|
||||
|
|
@ -889,6 +913,7 @@
|
|||
<string name="token_swap_promotion_button">Обменять</string>
|
||||
<string name="tokens_list_unavailable_to_purchase_header">Недоступно для покупки</string>
|
||||
<string name="tokens_list_unavailable_to_sell_header">Недоступно для продажи</string>
|
||||
<string name="tokens_list_unavailable_to_swap_header">Недоступно для обмена с %s</string>
|
||||
<string name="transaction_history_contract_address">контракт: %s</string>
|
||||
<string name="transaction_history_empty_transactions">У вас еще нет транзакций</string>
|
||||
<string name="transaction_history_error_failed_to_load">Не удалось загрузить историю транзакций.\nНажмите на кнопку перезагрузки, чтобы обновить информацию.</string>
|
||||
|
|
@ -966,7 +991,7 @@
|
|||
<string name="wallet_notification_address_copied">Адрес скопирован в буфер обмена</string>
|
||||
<string name="wallet_notification_no_internet">Нет соединения с интернетом</string>
|
||||
<string name="wallet_promo_banner_button_title">Получить с 10%% скидкой</string>
|
||||
<string name="wallet_promo_banner_description">Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменяйте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. </string>
|
||||
<string name="wallet_promo_banner_description">Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменивайте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. </string>
|
||||
<string name="wallet_promo_banner_title">Откройте Tangem Wallet</string>
|
||||
<string name="wallet_settings_title">Настройки кошелька</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
|
|
@ -1009,12 +1034,12 @@
|
|||
<string name="warning_failed_to_verify_card_message">Возможно, данная карта является образцом или подделкой</string>
|
||||
<string name="warning_failed_to_verify_card_title">Ошибка проверки подлинности</string>
|
||||
<string name="warning_hedera_missing_token_association_button_title">Ассоциировать</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s </string>
|
||||
<string name="warning_hedera_missing_token_association_message">Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять. Стоимость ассоциации ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message_brief">Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять</string>
|
||||
<string name="warning_hedera_missing_token_association_title">Ассоциируете свой токен</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Вы уверены что хотите отменить эту транзакцию? Вы не сможете отправить ее еще раз</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Ваша транзакция %1$s %2$s не была завершена. Вы можете попробовать отправить ее еще раз.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Ваша транзакция %1$s %2$s не была завершена. Вы можете попробовать отправить ее еще раз.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_title">У вас есть незавершенная транзакция</string>
|
||||
<string name="warning_low_signatures_message">На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства.</string>
|
||||
<string name="warning_low_signatures_title">Малое количество подписей</string>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@
|
|||
<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>
|
||||
|
|
@ -132,7 +130,7 @@
|
|||
<string name="common_fee_selector_option_market">За ринком</string>
|
||||
<string name="common_fee_selector_option_slow">Повільно</string>
|
||||
<string name="common_fee_selector_title">Швидкість та комісія</string>
|
||||
<string name="common_generate_addresses">Отримати ваші адреси</string>
|
||||
<string name="common_generate_addresses">Синхронізувати адреси</string>
|
||||
<string name="common_go_to_provider">Перейти до провайдера</string>
|
||||
<string name="common_go_to_token">Перейти до токену</string>
|
||||
<string name="common_hour">година</string>
|
||||
|
|
@ -301,7 +299,7 @@
|
|||
<string name="express_provider_permission_needed">Потрібен дозвіл</string>
|
||||
<string name="express_provider_recommended">Рекомендовано</string>
|
||||
<string name="express_status_bought">Купив %s</string>
|
||||
<string name="express_status_buying">Купівля %s...</string>
|
||||
<string name="express_status_buying">Купівля %s</string>
|
||||
<string name="express_status_buying_active">Купівля %s...</string>
|
||||
<string name="express_token_list_empty_search">Токенів не знайдено. Будь ласка, спробуйте інший запит</string>
|
||||
<string name="express_transaction_id">ID: %s</string>
|
||||
|
|
@ -588,10 +586,11 @@
|
|||
<string name="onramp_min_amount_restriction">Сума покупки повинна бути не менше %s</string>
|
||||
<string name="onramp_no_available_providers">Для данної валюти немає доступних провайдерів</string>
|
||||
<string name="onramp_pay_with">Оплата з</string>
|
||||
<string name="onramp_provider_max_amount">Доступно до %s</string>
|
||||
<string name="onramp_provider_min_amount">Доступно від %s</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">Ви зможете завершити транзакцію у стороннього постачальника, %s</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Перенаправлення на %s...</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">Наші сервіси недоступні в цій країні</string>
|
||||
<string name="onramp_residency_bottomsheet_country_subtitle">Змінити або підтвердити</string>
|
||||
<string name="onramp_residency_bottomsheet_title">Ваше місце проживання визначено як</string>
|
||||
<string name="onramp_settings_residence_description">Будь ласка, виберіть правильну країну, щоб забезпечити точні способи оплати та послуги.</string>
|
||||
<string name="onramp_settings_title">Параметри</string>
|
||||
|
|
@ -1023,12 +1022,12 @@
|
|||
<string name="warning_failed_to_verify_card_message">Ця картка може бути виробничим зразком або підробкою</string>
|
||||
<string name="warning_failed_to_verify_card_title">Перевірка автентичності не вдалася</string>
|
||||
<string name="warning_hedera_missing_token_association_button_title">Асоціювати</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Цей токен повинен бути асоційований з вашим обліковим записом Hedera, перш ніж ви зможете його прийняти. Вартість асоціації ~ %1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message">Цей токен повинен бути асоційований з вашим обліковим записом Hedera, перш ніж ви зможете його прийняти. Вартість асоціації ~ %1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message_brief">Цей токен повинен бути асоційований з вашим обліковим записом Hedera, перш ніж ви зможете його прийняти</string>
|
||||
<string name="warning_hedera_missing_token_association_title">Асоціюйте свій токен</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">Недостатньо %s. Поповніть ваш обліковий запис Hedera для асоціації цього токена</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Ви впевнені, що хочете скасувати цю транзакцію? Ви не зможете відправити її ще раз</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Ваша транзакція %1$s %2$s не була завершена. Ви можете спробувати відправити її ще раз.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Ваша транзакція %1$s %2$s не була завершена. Ви можете спробувати відправити її ще раз.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_title">У вас є незавершена транзакція</string>
|
||||
<string name="warning_low_signatures_message">На цій картці залишається лише %s підписів. Ви повинні вивести всі свої кошти.</string>
|
||||
<string name="warning_low_signatures_title">Низька кількість підписів</string>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="action_buttons_buy_empty_search_message">Token not found in your portfolio? Check the Markets to find and add it for purchase</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Token not found in your portfolio? Check the Markets to find and add it for sell</string>
|
||||
<string name="action_buttons_buy_empty_search_message">Can’t find your token? Go to the Market section on the main page and add it to your portfolio for purchase</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Can’t find your token? Go to the Market section on the main page and add it to your portfolio for selling.</string>
|
||||
<string name="action_buttons_sell_navigation_bar_title">Sell crypto</string>
|
||||
<string name="action_buttons_swap_choose_token">Choose the Token</string>
|
||||
<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_empty_search_message">Can’t find your token? Go to the Market section on the main page and add it to your portfolio for swapping.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_message">There are no available tokens to swap with the selected token. Please choose another one.</string>
|
||||
<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="action_buttons_you_want_to_receive">Choose the token you want to receive</string>
|
||||
<string name="action_buttons_you_want_to_swap">Choose the token 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>
|
||||
|
|
@ -126,7 +127,7 @@
|
|||
<string name="common_fee_selector_option_market">Market</string>
|
||||
<string name="common_fee_selector_option_slow">Slow</string>
|
||||
<string name="common_fee_selector_title">Speed and fee</string>
|
||||
<string name="common_generate_addresses">Get your addresses</string>
|
||||
<string name="common_generate_addresses">Synchronize addresses</string>
|
||||
<string name="common_go_to_provider">Go to provider</string>
|
||||
<string name="common_go_to_token">Go to token</string>
|
||||
<string name="common_hour">hour</string>
|
||||
|
|
@ -251,6 +252,7 @@
|
|||
<string name="express_choose_providers_subtitle">Tangem offers token swaps via 3rd-party providers according to each provider\'s terms</string>
|
||||
<string name="express_choose_providers_title">Choose provider</string>
|
||||
<string name="express_error_code">An error occurred. Code: %s</string>
|
||||
<string name="express_error_provider_amount_roundup">Error %1$s. The selected provider cannot process the specified transaction. Please round the value up to %2$s or change it</string>
|
||||
<string name="express_error_swap_pair_unavailable">Selected provider is unavailable at the moment. Please try again later. (Code: %s)</string>
|
||||
<string name="express_error_swap_unavailable">Swaps are unavailable at the moment. Please try again later. (Code: %s)</string>
|
||||
<string name="express_estimated_amount">Estimated amount</string>
|
||||
|
|
@ -565,6 +567,8 @@
|
|||
<string name="onboarding_wallet_info_title_fourth">Access code restore</string>
|
||||
<string name="onboarding_wallet_info_title_second">Identical cards</string>
|
||||
<string name="onboarding_wallet_info_title_third">Access code</string>
|
||||
<string name="onramp_avaiable_with_payment_methods">Available with %s</string>
|
||||
<string name="onramp_choose_provider_title_hint">Buying crypto in Tangem is powered by third-party providers on their terms.</string>
|
||||
<string name="onramp_country_search">Search by country</string>
|
||||
<string name="onramp_country_unavailable">Unavailable</string>
|
||||
<string name="onramp_currency_search">Search by currency</string>
|
||||
|
|
@ -572,15 +576,17 @@
|
|||
<string name="onramp_min_amount_restriction">The amount to buy must be at least %s</string>
|
||||
<string name="onramp_no_available_providers">No available providers for this currency</string>
|
||||
<string name="onramp_pay_with">Pay with</string>
|
||||
<string name="onramp_provider_max_amount">Available up to %s</string>
|
||||
<string name="onramp_provider_min_amount">Available from %s</string>
|
||||
<string name="onramp_redirecting_to_provider_subtitle">You will be able to complete your transaction on the third-party provider, %s</string>
|
||||
<string name="onramp_redirecting_to_provider_title">Redirecting to %s...</string>
|
||||
<string name="onramp_residency_bottomsheet_country_not_supported">Our services are not available in this country</string>
|
||||
<string name="onramp_residency_bottomsheet_country_subtitle">Change or confirm it</string>
|
||||
<string name="onramp_residency_bottomsheet_title">Your residence has been identified as</string>
|
||||
<string name="onramp_settings_residence">Residence</string>
|
||||
<string name="onramp_settings_residence_description">Please select the correct country to ensure accurate payment options and services.</string>
|
||||
<string name="onramp_settings_title">Settings</string>
|
||||
<string name="onramp_transaction_status_footer_text">You can check the transaction status from the token page</string>
|
||||
<string name="onramp_statuses_view_footer">You can check the transaction status from the token page.</string>
|
||||
<string name="onramp_transaction_status_footer_text">You can close this screen and check the transaction status on the token details screen.</string>
|
||||
<string name="onramp_via">Via</string>
|
||||
<string name="organize_tokens_group">Group</string>
|
||||
<string name="organize_tokens_sort_by_balance">By balance</string>
|
||||
|
|
@ -590,8 +596,11 @@
|
|||
<string name="qr_scanner_camera_denied_settings_button">Settings</string>
|
||||
<string name="qr_scanner_camera_denied_text">You have not given access to your camera</string>
|
||||
<string name="qr_scanner_camera_denied_title">Camera access denied</string>
|
||||
<string name="receive_bottom_sheet_no_memo_required_message">No memo required</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%1$s (%2$s) on %3$s network</string>
|
||||
<string name="receive_bottom_sheet_warning_message_description">Sending any other currency will result in its irreversible loss.</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">Send only %s to this address. Sending any other currency will result in its irreversible loss.</string>
|
||||
<string name="receive_bottom_sheet_warning_message_title">Send only %s to this address.</string>
|
||||
<string name="receive_token_description">Transfer funds from any wallet or exchange</string>
|
||||
<string name="referral_button_participate">Participate</string>
|
||||
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
|
||||
|
|
@ -643,6 +652,10 @@
|
|||
<string name="scan_card_settings_button">Scan card or ring</string>
|
||||
<string name="scan_card_settings_message">Scan the card or ring to change its settings. The changes will impact only the card or ring you\'ve scanned and will not affect other devices tied to your wallet.</string>
|
||||
<string name="scan_card_settings_title">Get your Tangem ready!</string>
|
||||
<string name="selling_insufficient_balance_alert_message">You don’t have enough funds in your balance to sell this cryptocurrency. Please deposit the desired asset to proceed.</string>
|
||||
<string name="selling_insufficient_balance_alert_title">Insufficient Balance</string>
|
||||
<string name="selling_regional_restriction_alert_message">Selling cryptocurrency is unavailable in your region at the moment. We’re actively working to bring this option to you soon—stay tuned!</string>
|
||||
<string name="selling_regional_restriction_alert_title">Regional Restriction</string>
|
||||
<string name="send_additional_field_already_included">Already included in the entered address</string>
|
||||
<string name="send_alert_fee_too_high_text">Your commission amount is %s times higher than the recommended amount. Please review and adjust your custom settings.</string>
|
||||
<string name="send_alert_fee_too_low_text">You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue?</string>
|
||||
|
|
@ -683,6 +696,7 @@
|
|||
<string name="send_notification_fee_too_high_title">Custom fee is high</string>
|
||||
<string name="send_notification_high_fee_text">Due to the peculiarities of the %1$s network, the fee for transferring the entire balance is higher. To reduce the commission, you can leave %2$s.</string>
|
||||
<string name="send_notification_high_fee_title">The fee is higher</string>
|
||||
<string name="send_notification_invalid_amount_rent_fee">Your account balance cannot be lower than the rent fee. Please maintain at least %1$s on your account or withdraw all funds.</string>
|
||||
<string name="send_notification_invalid_amount_text">The included commission exceeds the transfer amount, leading to a negative value</string>
|
||||
<string name="send_notification_invalid_amount_title">Invalid amount</string>
|
||||
<string name="send_notification_invalid_minimum_amount_text">The minimum sending amount is %1$s. Please ensure that the remaining balance after sending will not be less than %2$s.</string>
|
||||
|
|
@ -1004,12 +1018,12 @@
|
|||
<string name="warning_failed_to_verify_card_message">This card might be a production sample or counterfeit</string>
|
||||
<string name="warning_failed_to_verify_card_title">Authenticity check failed</string>
|
||||
<string name="warning_hedera_missing_token_association_button_title">Associate</string>
|
||||
<string name="warning_hedera_missing_token_association_message">This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message">This token must be associated with your Hedera account before you can receive it. Association fee ~%1$s %2$s</string>
|
||||
<string name="warning_hedera_missing_token_association_message_brief">This token must be associated with your Hedera account before you can receive it</string>
|
||||
<string name="warning_hedera_missing_token_association_title">Associate your token</string>
|
||||
<string name="warning_hedera_token_association_not_enough_hbar_message">Not enough %s. Top up your Hedera account to associate this token</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Are you sure you want to cancel the transaction? You will not be able to retry the transaction again</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Your transaction with an amount of %1$s %2$s was not completed. You can try again to complete it.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_message">Your transaction with an amount of %1$s %2$s was not completed. You can try again to complete it.</string>
|
||||
<string name="warning_kaspa_unfinished_token_transaction_title">You have unfinished transaction</string>
|
||||
<string name="warning_low_signatures_message">Only %s signatures are left on this card. You must withdraw all of your funds.</string>
|
||||
<string name="warning_low_signatures_title">Low signature count</string>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -78,6 +79,7 @@ fun ActionButton(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun Button(
|
||||
config: ActionButtonConfig,
|
||||
|
|
@ -85,6 +87,7 @@ private fun Button(
|
|||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.button.secondary,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (config.enabled) color else TangemTheme.colors.button.disabled,
|
||||
label = "Update background color",
|
||||
|
|
@ -92,30 +95,9 @@ private fun Button(
|
|||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size36)
|
||||
.heightIn(min = 36.dp)
|
||||
.widthIn(min = 100.dp)
|
||||
.clip(shape)
|
||||
.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,
|
||||
|
|
@ -128,38 +110,55 @@ private fun Content(config: ActionButtonConfig) {
|
|||
}
|
||||
},
|
||||
)
|
||||
.padding(start = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing24)
|
||||
.background(color = backgroundColor),
|
||||
) {
|
||||
Content(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
config = config,
|
||||
)
|
||||
|
||||
if (config.isInProgress) {
|
||||
Loading(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.Center)
|
||||
.matchParentSize(),
|
||||
backgroundColor = backgroundColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(config: ActionButtonConfig, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing24,
|
||||
)
|
||||
.padding(vertical = TangemTheme.dimens.spacing8),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val iconTint by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.icon.informative
|
||||
config.dimContent -> TangemTheme.colors.icon.informative
|
||||
else -> TangemTheme.colors.icon.primary1
|
||||
},
|
||||
label = "Update tint color",
|
||||
)
|
||||
|
||||
val iconTint = when {
|
||||
!config.enabled -> TangemTheme.colors.icon.informative
|
||||
config.dimContent -> TangemTheme.colors.icon.informative
|
||||
else -> TangemTheme.colors.icon.primary1
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.size(size = TangemTheme.dimens.size20),
|
||||
painter = painterResource(id = config.iconResId),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(size = TangemTheme.dimens.size20),
|
||||
tint = iconTint,
|
||||
)
|
||||
|
||||
SpacerW8()
|
||||
|
||||
val textColor by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.text.disabled
|
||||
config.dimContent -> TangemTheme.colors.text.tertiary
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
label = "Update text color",
|
||||
)
|
||||
|
||||
val textColor = when {
|
||||
!config.enabled -> TangemTheme.colors.text.disabled
|
||||
config.dimContent -> TangemTheme.colors.text.tertiary
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
}
|
||||
Text(
|
||||
text = config.text.resolveReference(),
|
||||
color = textColor,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ inline fun RowContentContainer(
|
|||
}
|
||||
|
||||
@Composable
|
||||
internal fun RowText(
|
||||
fun RowText(
|
||||
mainText: String,
|
||||
secondText: String,
|
||||
accentMainText: Boolean,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ dependencies {
|
|||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.onramp)
|
||||
implementation(projects.domain.legacy)
|
||||
|
||||
// region DI
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.data.onramp
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.extensions.toBigDecimalOrDefault
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.onramp.converters.CountryConverter
|
||||
import com.tangem.data.onramp.converters.CurrencyConverter
|
||||
|
|
@ -12,13 +14,16 @@ import com.tangem.datasource.api.common.response.getOrThrow
|
|||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.TangemExpressValues
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeProvider
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeProviderType
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
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.OnrampDataJson
|
||||
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
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||
import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore
|
||||
import com.tangem.datasource.local.onramp.quotes.OnrampQuotesStore
|
||||
|
|
@ -28,20 +33,26 @@ 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.*
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.joda.time.DateTime
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
|
||||
internal class DefaultOnrampRepository(
|
||||
private val onrampApi: OnrampApi,
|
||||
private val expressApi: TangemExpressApi,
|
||||
|
|
@ -51,12 +62,16 @@ internal class DefaultOnrampRepository(
|
|||
private val pairsStore: OnrampPairsStore,
|
||||
private val quotesStore: OnrampQuotesStore,
|
||||
private val quotesErrorConverter: OnrampQuotesErrorConverter,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||
moshi: Moshi,
|
||||
) : OnrampRepository {
|
||||
|
||||
private val currencyConverter = CurrencyConverter()
|
||||
private val countryConverter = CountryConverter(currencyConverter)
|
||||
private val statusConverter = StatusConverter()
|
||||
private val paymentMethodsConverter = PaymentMethodConverter()
|
||||
private val onrampDataAdapter = moshi.adapter(OnrampDataJson::class.java)
|
||||
|
||||
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
|
||||
onrampApi.getCurrencies()
|
||||
|
|
@ -194,14 +209,19 @@ internal class DefaultOnrampRepository(
|
|||
providerId = provider.id,
|
||||
).bind()
|
||||
OnrampQuote.Data(
|
||||
fromAmount = convertToBigDecimal(response.fromAmount),
|
||||
toAmount = convertToBigDecimal(response.toAmount),
|
||||
minFromAmount = convertToBigDecimal(response.minFromAmount),
|
||||
maxFromAmount = convertToBigDecimal(response.maxFromAmount),
|
||||
fromAmount = OnrampAmount(
|
||||
value = response.fromAmount
|
||||
.toBigDecimalOrDefault()
|
||||
.movePointLeft(currency.precision),
|
||||
decimals = currency.precision,
|
||||
symbol = amount.currencySymbol,
|
||||
),
|
||||
toAmount = convertToAmount(response.toAmount, cryptoCurrency),
|
||||
minFromAmount = convertToAmount(response.minFromAmount, cryptoCurrency),
|
||||
maxFromAmount = convertToAmount(response.maxFromAmount, cryptoCurrency),
|
||||
paymentMethod = paymentMethod,
|
||||
provider = provider,
|
||||
)
|
||||
null
|
||||
},
|
||||
onError = { error ->
|
||||
if (error is ApiResponseError.HttpException) {
|
||||
|
|
@ -230,6 +250,69 @@ internal class DefaultOnrampRepository(
|
|||
return quotesStore.get(QUOTES_KEY)
|
||||
}
|
||||
|
||||
override suspend fun getQuotesSync(): List<OnrampQuote>? {
|
||||
return quotesStore.getSyncOrNull(QUOTES_KEY)
|
||||
}
|
||||
|
||||
override suspend fun getOnrampData(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
quote: OnrampProviderWithQuote.Data,
|
||||
): OnrampTransaction = withContext(dispatchers.io) {
|
||||
try {
|
||||
val address = requireNotNull(
|
||||
value = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network),
|
||||
lazyMessage = { "Address must not be null" },
|
||||
)
|
||||
val fromAmountString = quote.fromAmount.value.movePointRight(quote.fromAmount.decimals).toString()
|
||||
val currency = requireNotNull(getDefaultCurrencySync()) { "Default currency must not be null" }
|
||||
val country = requireNotNull(getDefaultCountrySync()) { "Default country must not be null" }
|
||||
val data = safeApiCall(
|
||||
call = {
|
||||
onrampApi.getData(
|
||||
fromCurrencyCode = currency.code,
|
||||
toContractAddress = cryptoCurrency.getContractAddress(),
|
||||
toNetwork = cryptoCurrency.network.backendId,
|
||||
paymentMethod = quote.paymentMethod.id,
|
||||
countryCode = country.code,
|
||||
fromAmount = fromAmountString,
|
||||
toDecimals = cryptoCurrency.decimals,
|
||||
providerId = quote.provider.id,
|
||||
toAddress = address,
|
||||
redirectUrl = "tangem://redirect?action=dismissBrowser",
|
||||
language = null,
|
||||
theme = null,
|
||||
requestId = UUID.randomUUID().toString(),
|
||||
).bind()
|
||||
},
|
||||
onError = { e ->
|
||||
Timber.e(e)
|
||||
throw e
|
||||
},
|
||||
)
|
||||
if (dataSignatureVerifier.verifySignature(data.signature, data.dataJson)) {
|
||||
val dataJson = requireNotNull(onrampDataAdapter.fromJson(data.dataJson)) {
|
||||
"Can not parse dataJson. ${data.dataJson}"
|
||||
}
|
||||
createOnrampTransaction(
|
||||
txId = data.txId,
|
||||
onrampDataJson = dataJson,
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
provider = quote.provider,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
fromAmount = quote.fromAmount.value,
|
||||
toAmount = quote.toAmount.value,
|
||||
)
|
||||
} else {
|
||||
error("Express signature invalid")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPaymentMethods(): List<OnrampPaymentMethod> {
|
||||
val paymentMethods = requireNotNull(paymentMethodsStore.getSyncOrNull(PAYMENT_METHODS_KEY)) {
|
||||
"Onramp payment methods is absent in storage"
|
||||
|
|
@ -237,6 +320,17 @@ internal class DefaultOnrampRepository(
|
|||
return paymentMethodsConverter.convertList(paymentMethods)
|
||||
}
|
||||
|
||||
override suspend fun saveSelectedPaymentMethod(paymentMethod: OnrampPaymentMethod) {
|
||||
val dto = paymentMethodsConverter.convertBack(paymentMethod)
|
||||
paymentMethodsStore.store(SELECTED_PAYMENT_METHOD_KEY, listOf(dto))
|
||||
}
|
||||
|
||||
override fun getSelectedPaymentMethod(): Flow<OnrampPaymentMethod> {
|
||||
return paymentMethodsStore
|
||||
.get(SELECTED_PAYMENT_METHOD_KEY)
|
||||
.mapNotNull { paymentMethods -> paymentMethods.firstOrNull()?.let(paymentMethodsConverter::convert) }
|
||||
}
|
||||
|
||||
override suspend fun clearCache() = withContext(NonCancellable) {
|
||||
paymentMethodsStore.clear()
|
||||
pairsStore.clear()
|
||||
|
|
@ -264,18 +358,53 @@ internal class DefaultOnrampRepository(
|
|||
pairsStore.store(PAIRS_KEY, onrampPairs)
|
||||
}
|
||||
|
||||
private fun createOnrampTransaction(
|
||||
txId: String,
|
||||
onrampDataJson: OnrampDataJson,
|
||||
userWalletId: UserWalletId,
|
||||
currency: OnrampCurrency,
|
||||
provider: OnrampProvider,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
fromAmount: BigDecimal,
|
||||
toAmount: BigDecimal,
|
||||
): OnrampTransaction {
|
||||
return OnrampTransaction(
|
||||
txId = txId,
|
||||
userWalletId = userWalletId,
|
||||
fromAmount = fromAmount,
|
||||
fromCurrency = currency,
|
||||
toAmount = toAmount,
|
||||
toCurrencyId = cryptoCurrency.id.value,
|
||||
status = OnrampStatus.Status.Expired,
|
||||
externalTxUrl = onrampDataJson.externalTxUrl,
|
||||
externalTxId = onrampDataJson.externalTxId,
|
||||
timestamp = DateTime.now().millis,
|
||||
providerName = provider.info.name,
|
||||
providerImageUrl = provider.info.imageLarge,
|
||||
providerType = ExchangeProviderType.ONRAMP.name,
|
||||
redirectUrl = onrampDataJson.widgetUrl,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.getContractAddress(): String = when (this) {
|
||||
is CryptoCurrency.Coin -> TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
|
||||
is CryptoCurrency.Token -> this.contractAddress
|
||||
}
|
||||
|
||||
private fun convertToBigDecimal(value: String): BigDecimal =
|
||||
requireNotNull(value.toBigDecimalOrNull()) { "Can not parse $value to BigDecimal" }
|
||||
private fun convertToAmount(value: String, cryptoCurrency: CryptoCurrency): OnrampAmount {
|
||||
val cryptoValue = requireNotNull(value.toBigDecimalOrNull()) { "Can not parse $value to BigDecimal" }
|
||||
return OnrampAmount(
|
||||
value = cryptoValue.movePointLeft(cryptoCurrency.decimals),
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<PaymentMethodDTO>.removeApplePay(): List<PaymentMethodDTO> = filterNot { it.id == "apple-pay" }
|
||||
|
||||
private companion object {
|
||||
const val PAYMENT_METHODS_KEY = "onramp_payment_methods"
|
||||
const val SELECTED_PAYMENT_METHOD_KEY = "onramp_selected_payment_method"
|
||||
const val PAIRS_KEY = "onramp_pairs"
|
||||
const val QUOTES_KEY = "onramp_quotes"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ package com.tangem.data.onramp.converters
|
|||
|
||||
import com.tangem.datasource.api.onramp.models.response.model.PaymentMethodDTO
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class PaymentMethodConverter : Converter<PaymentMethodDTO, OnrampPaymentMethod> {
|
||||
internal class PaymentMethodConverter : TwoWayConverter<PaymentMethodDTO, OnrampPaymentMethod> {
|
||||
override fun convert(value: PaymentMethodDTO): OnrampPaymentMethod = OnrampPaymentMethod(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
imageUrl = value.image,
|
||||
)
|
||||
|
||||
override fun convertBack(value: OnrampPaymentMethod): PaymentMethodDTO = PaymentMethodDTO(
|
||||
id = value.id,
|
||||
name = value.name,
|
||||
image = value.imageUrl,
|
||||
)
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ internal class TransactionConverter : TwoWayConverter<OnrampTransactionDTO, Onra
|
|||
providerName = value.providerName,
|
||||
providerImageUrl = value.providerImageUrl,
|
||||
providerType = value.providerType,
|
||||
|
||||
redirectUrl = "", // not necessary field
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ package com.tangem.data.onramp.converters.error
|
|||
import com.squareup.moshi.JsonAdapter
|
||||
import com.tangem.datasource.api.express.models.response.ExpressError
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.domain.onramp.model.OnrampAmount
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class OnrampQuotesErrorConverter(
|
||||
|
|
@ -40,7 +39,6 @@ internal class OnrampQuotesErrorConverter(
|
|||
amountWithOffset = minAmount,
|
||||
decimals = decimals,
|
||||
symbol = input.amount.currencySymbol,
|
||||
type = input.amount.type,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -59,24 +57,17 @@ internal class OnrampQuotesErrorConverter(
|
|||
amountWithOffset = maxAmount,
|
||||
decimals = decimals,
|
||||
symbol = input.amount.currencySymbol,
|
||||
type = input.amount.type,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createFromAmountWithOffset(
|
||||
amountWithOffset: String,
|
||||
decimals: Int,
|
||||
symbol: String,
|
||||
type: AmountType,
|
||||
): Amount {
|
||||
return Amount(
|
||||
private fun createFromAmountWithOffset(amountWithOffset: String, decimals: Int, symbol: String): OnrampAmount {
|
||||
return OnrampAmount(
|
||||
value = requireNotNull(
|
||||
amountWithOffset.toBigDecimalOrNull()?.movePointLeft(decimals),
|
||||
) { "wrong amount format, use only digits" },
|
||||
decimals = decimals,
|
||||
currencySymbol = symbol,
|
||||
type = type,
|
||||
symbol = symbol,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.data.onramp.converters.error.OnrampQuotesErrorConverter
|
|||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.datasource.api.onramp.OnrampApi
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.onramp.pairs.OnrampPairsStore
|
||||
import com.tangem.datasource.local.onramp.paymentmethods.OnrampPaymentMethodsStore
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -38,6 +40,9 @@ internal object OnrampDataModule {
|
|||
pairsStore: OnrampPairsStore,
|
||||
quotesStore: OnrampQuotesStore,
|
||||
quotesErrorConverter: OnrampQuotesErrorConverter,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
dataSignatureVerifier: DataSignatureVerifier,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): OnrampRepository {
|
||||
return DefaultOnrampRepository(
|
||||
onrampApi = onrampApi,
|
||||
|
|
@ -48,6 +53,9 @@ internal object OnrampDataModule {
|
|||
pairsStore = pairsStore,
|
||||
quotesStore = quotesStore,
|
||||
quotesErrorConverter = quotesErrorConverter,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
dataSignatureVerifier = dataSignatureVerifier,
|
||||
moshi = moshi,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package com.tangem.domain.managetokens.model
|
|||
sealed class AddCustomTokenForm {
|
||||
|
||||
data class Raw(
|
||||
val contractAddress: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val decimals: String,
|
||||
val contractAddress: String = "",
|
||||
val name: String = "",
|
||||
val symbol: String = "",
|
||||
val decimals: String = "",
|
||||
) : AddCustomTokenForm()
|
||||
|
||||
sealed class Validated : AddCustomTokenForm() {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,27 @@
|
|||
package com.tangem.domain.models.scan
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.common.card.WalletData
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.domain.models.scan.serialization.ScanResponseAsStringSerializer
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
// TODO: Move to :domain:card:models
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable(with = ScanResponseAsStringSerializer::class)
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ScanResponse(
|
||||
val card: CardDTO,
|
||||
val productType: ProductType,
|
||||
val walletData: WalletData?,
|
||||
val secondTwinPublicKey: String? = null,
|
||||
val derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null,
|
||||
@Json(name = "card") val card: CardDTO,
|
||||
@Json(name = "productType") val productType: ProductType,
|
||||
@Json(name = "walletData") val walletData: WalletData?,
|
||||
@Json(name = "secondTwinPublicKey") val secondTwinPublicKey: String? = null,
|
||||
@Json(name = "derivedKeys") val derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
@Json(name = "primaryCard") val primaryCard: PrimaryCard? = null,
|
||||
)
|
||||
|
||||
typealias KeyWalletPublicKey = ByteArrayKey
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.domain.userWalletList.utils.json
|
||||
package com.tangem.domain.models.scan.serialization
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
|
|
@ -7,7 +7,7 @@ import com.squareup.moshi.JsonWriter
|
|||
import com.squareup.moshi.ToJson
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
|
||||
internal class ByteArrayKeyAdapter {
|
||||
class ByteArrayKeyAdapter {
|
||||
@ToJson
|
||||
fun toJson(writer: JsonWriter, src: ByteArrayKey, byteArrayAdapter: JsonAdapter<ByteArray>) {
|
||||
byteArrayAdapter.toJson(writer, src.bytes)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.domain.userWalletList.utils.json
|
||||
package com.tangem.domain.models.scan.serialization
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
|
|
@ -7,7 +7,7 @@ import com.squareup.moshi.JsonWriter
|
|||
import com.squareup.moshi.ToJson
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
||||
internal class CardBackupStatusAdapter {
|
||||
class CardBackupStatusAdapter {
|
||||
@ToJson
|
||||
fun toJson(writer: JsonWriter, src: CardDTO.BackupStatus?, mapAdapter: JsonAdapter<Map<String, String>>) {
|
||||
val jsonMap = mutableMapOf<String, String>()
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.domain.userWalletList.utils.json
|
||||
package com.tangem.domain.models.scan.serialization
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.ToJson
|
||||
|
|
@ -6,7 +6,6 @@ import com.tangem.common.extensions.guard
|
|||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.crypto.hdWallet.DerivationNode
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import timber.log.Timber
|
||||
|
||||
class DerivationPathAdapterWithMigration {
|
||||
@ToJson
|
||||
|
|
@ -26,7 +25,6 @@ class DerivationPathAdapterWithMigration {
|
|||
private fun fromLegacyScheme(jsonMap: Map<String, Any>): DerivationPath {
|
||||
val rawPath = jsonMap["rawPath"] as String
|
||||
val nodeIndexes = (jsonMap["nodes"] as? List<Number>).guard {
|
||||
Timber.e("Unable to convert derivation path nodes from JSON")
|
||||
return DerivationPath(rawPath)
|
||||
}
|
||||
val nodes = nodeIndexes.map { DerivationNode.fromIndex(it.toLong()) }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.domain.userWalletList.utils.json
|
||||
package com.tangem.domain.models.scan.serialization
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
|
|
@ -11,7 +11,7 @@ import com.tangem.crypto.hdWallet.DerivationPath
|
|||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
internal class ExtendedPublicKeysMapAdapter {
|
||||
class ExtendedPublicKeysMapAdapter {
|
||||
@ToJson
|
||||
fun toJson(
|
||||
writer: JsonWriter,
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.tangem.domain.models.scan.serialization
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import com.tangem.common.json.TangemSdkAdapter
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
internal object ScanResponseAsStringSerializer : KSerializer<ScanResponse> {
|
||||
private val moshi = Moshi.Builder()
|
||||
.add(WalletDerivedKeysMapAdapter())
|
||||
.add(ScanResponseDerivedKeysMapAdapter())
|
||||
.add(ByteArrayKeyAdapter())
|
||||
.add(ExtendedPublicKeysMapAdapter())
|
||||
.add(CardBackupStatusAdapter())
|
||||
.add(DerivationPathAdapterWithMigration())
|
||||
.add(TangemSdkAdapter.DateAdapter())
|
||||
.add(TangemSdkAdapter.DerivationNodeAdapter())
|
||||
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ScanResponse", PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): ScanResponse {
|
||||
return moshi.adapter(ScanResponse::class.java).fromJson(decoder.decodeString())!!
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ScanResponse) {
|
||||
encoder.encodeString(moshi.adapter(ScanResponse::class.java).toJson(value)!!)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.domain.userWalletList.utils.json
|
||||
package com.tangem.domain.models.scan.serialization
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
|
|
@ -8,7 +8,7 @@ import com.squareup.moshi.ToJson
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
internal class ScanResponseDerivedKeysMapAdapter {
|
||||
class ScanResponseDerivedKeysMapAdapter {
|
||||
@ToJson
|
||||
fun toJson(
|
||||
writer: JsonWriter,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.tap.domain.userWalletList.utils.json
|
||||
package com.tangem.domain.models.scan.serialization
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
|
|
@ -10,7 +10,7 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
|
||||
internal class WalletDerivedKeysMapAdapter {
|
||||
class WalletDerivedKeysMapAdapter {
|
||||
@ToJson
|
||||
fun toJson(
|
||||
writer: JsonWriter,
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import com.tangem.domain.core.serialization.SerializedBigDecimal
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OnrampAmount(
|
||||
val symbol: String,
|
||||
val value: SerializedBigDecimal,
|
||||
val decimals: Int,
|
||||
)
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OnrampProvider(
|
||||
val id: String,
|
||||
val info: OnrampProviderInfo,
|
||||
val paymentMethods: List<OnrampPaymentMethod>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class OnrampProviderInfo(val name: String, val imageLarge: String)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
sealed interface OnrampProviderWithQuote {
|
||||
|
||||
val provider: OnrampProvider
|
||||
|
||||
@Serializable
|
||||
data class Data(
|
||||
override val provider: OnrampProvider,
|
||||
val paymentMethod: OnrampPaymentMethod,
|
||||
val toAmount: OnrampAmount,
|
||||
val fromAmount: OnrampAmount,
|
||||
) : OnrampProviderWithQuote
|
||||
|
||||
sealed interface Unavailable : OnrampProviderWithQuote {
|
||||
|
||||
data class NotSupportedPaymentMethod(
|
||||
override val provider: OnrampProvider,
|
||||
val availablePaymentMethods: List<OnrampPaymentMethod>,
|
||||
) : Unavailable
|
||||
|
||||
data class AvailableFrom(override val provider: OnrampProvider, val amount: OnrampAmount) : Unavailable
|
||||
data class AvailableUpTo(override val provider: OnrampProvider, val amount: OnrampAmount) : Unavailable
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
package com.tangem.domain.onramp.model
|
||||
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class OnrampQuote {
|
||||
|
||||
abstract val paymentMethod: OnrampPaymentMethod
|
||||
|
|
@ -11,10 +8,10 @@ sealed class OnrampQuote {
|
|||
data class Data(
|
||||
override val paymentMethod: OnrampPaymentMethod,
|
||||
override val provider: OnrampProvider,
|
||||
val fromAmount: BigDecimal,
|
||||
val toAmount: BigDecimal,
|
||||
val minFromAmount: BigDecimal,
|
||||
val maxFromAmount: BigDecimal,
|
||||
val fromAmount: OnrampAmount,
|
||||
val toAmount: OnrampAmount,
|
||||
val minFromAmount: OnrampAmount,
|
||||
val maxFromAmount: OnrampAmount,
|
||||
) : OnrampQuote()
|
||||
|
||||
sealed class Error : OnrampQuote() {
|
||||
|
|
@ -22,13 +19,13 @@ sealed class OnrampQuote {
|
|||
data class AmountTooSmallError(
|
||||
override val paymentMethod: OnrampPaymentMethod,
|
||||
override val provider: OnrampProvider,
|
||||
val amount: Amount,
|
||||
val amount: OnrampAmount,
|
||||
) : Error()
|
||||
|
||||
data class AmountTooBigError(
|
||||
override val paymentMethod: OnrampPaymentMethod,
|
||||
override val provider: OnrampProvider,
|
||||
val amount: Amount,
|
||||
val amount: OnrampAmount,
|
||||
) : Error()
|
||||
}
|
||||
}
|
||||
|
|
@ -36,4 +36,5 @@ data class OnrampTransaction(
|
|||
val providerName: String, // todo onramp fix after SwapProvider moved to own module
|
||||
val providerImageUrl: String, // todo onramp fix after SwapProvider moved to own module
|
||||
val providerType: String, // todo onramp fix after SwapProvider moved to own module
|
||||
val redirectUrl: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.OnrampProvider
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote.Unavailable
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class GetOnrampProviderWithQuoteUseCase(private val repository: OnrampRepository) {
|
||||
|
||||
suspend operator fun invoke(paymentMethod: OnrampPaymentMethod): Either<Throwable, List<OnrampProviderWithQuote>> {
|
||||
return Either.catch {
|
||||
val quotes: List<OnrampQuote> = requireNotNull(repository.getQuotesSync()) { "Quotes must not be null" }
|
||||
quotes
|
||||
.groupBy { it.provider }
|
||||
.mapNotNull { (provider, quotes) ->
|
||||
quotes.quoteWithProvider(provider = provider, selectedPaymentMethod = paymentMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<OnrampQuote>.quoteWithProvider(
|
||||
provider: OnrampProvider,
|
||||
selectedPaymentMethod: OnrampPaymentMethod,
|
||||
): OnrampProviderWithQuote? {
|
||||
val quoteData = this.filterIsInstance<OnrampQuote.Data>()
|
||||
val matchedPaymentMethodQuote = quoteData.firstOrNull { it.paymentMethod == selectedPaymentMethod }
|
||||
|
||||
if (matchedPaymentMethodQuote != null) {
|
||||
return OnrampProviderWithQuote.Data(
|
||||
provider = matchedPaymentMethodQuote.provider,
|
||||
paymentMethod = matchedPaymentMethodQuote.paymentMethod,
|
||||
toAmount = matchedPaymentMethodQuote.toAmount,
|
||||
fromAmount = matchedPaymentMethodQuote.fromAmount,
|
||||
)
|
||||
}
|
||||
|
||||
val paymentMethodNotSupported = quoteData.filterNot { it.paymentMethod == selectedPaymentMethod }
|
||||
if (paymentMethodNotSupported.isNotEmpty()) {
|
||||
return Unavailable.NotSupportedPaymentMethod(
|
||||
provider = provider,
|
||||
availablePaymentMethods = paymentMethodNotSupported.map(OnrampQuote.Data::paymentMethod),
|
||||
)
|
||||
}
|
||||
|
||||
val amountError = this
|
||||
.filterIsInstance<OnrampQuote.Error>()
|
||||
.firstOrNull { it.paymentMethod == selectedPaymentMethod }
|
||||
|
||||
return when {
|
||||
amountError != null -> {
|
||||
when (amountError) {
|
||||
is OnrampQuote.Error.AmountTooBigError -> Unavailable.AvailableUpTo(
|
||||
provider = amountError.provider,
|
||||
amount = amountError.amount,
|
||||
)
|
||||
is OnrampQuote.Error.AmountTooSmallError -> Unavailable.AvailableFrom(
|
||||
provider = amountError.provider,
|
||||
amount = amountError.amount,
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
class GetOnrampRedirectUrlUseCase(
|
||||
private val repository: OnrampRepository,
|
||||
private val transactionRepository: OnrampTransactionRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
quote: OnrampProviderWithQuote.Data,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Either<Throwable, String> {
|
||||
return Either.catch {
|
||||
val transaction = repository.getOnrampData(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
quote = quote,
|
||||
)
|
||||
transactionRepository.storeTransaction(transaction)
|
||||
transaction.redirectUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.core.utils.EitherFlow
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetOnrampSelectedPaymentMethodUseCase(private val repository: OnrampRepository) {
|
||||
|
||||
operator fun invoke(): EitherFlow<Throwable, OnrampPaymentMethod> {
|
||||
return repository.getSelectedPaymentMethod()
|
||||
.map<OnrampPaymentMethod, Either<Throwable, OnrampPaymentMethod>> { it.right() }
|
||||
.catch { emit(it.left()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.domain.onramp
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.repositories.OnrampRepository
|
||||
|
||||
class OnrampSaveSelectedPaymentMethod(private val repository: OnrampRepository) {
|
||||
|
||||
suspend operator fun invoke(paymentMethod: OnrampPaymentMethod): Either<Throwable, Unit> {
|
||||
return Either.catch { repository.saveSelectedPaymentMethod(paymentMethod) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
package com.tangem.domain.onramp.repositories
|
||||
|
||||
import com.tangem.domain.onramp.model.*
|
||||
import com.tangem.domain.onramp.model.cache.OnrampTransaction
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface OnrampRepository {
|
||||
// api
|
||||
suspend fun getCurrencies(): List<OnrampCurrency>
|
||||
|
|
@ -14,6 +17,11 @@ interface OnrampRepository {
|
|||
suspend fun fetchPaymentMethodsIfAbsent()
|
||||
suspend fun fetchPairs(currency: OnrampCurrency, country: OnrampCountry, cryptoCurrency: CryptoCurrency)
|
||||
suspend fun fetchQuotes(cryptoCurrency: CryptoCurrency, amount: Amount)
|
||||
suspend fun getOnrampData(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
quote: OnrampProviderWithQuote.Data,
|
||||
): OnrampTransaction
|
||||
|
||||
// cache
|
||||
suspend fun saveDefaultCurrency(currency: OnrampCurrency)
|
||||
|
|
@ -23,6 +31,9 @@ interface OnrampRepository {
|
|||
suspend fun getDefaultCountrySync(): OnrampCountry?
|
||||
fun getDefaultCountry(): Flow<OnrampCountry?>
|
||||
suspend fun getPaymentMethods(): List<OnrampPaymentMethod>
|
||||
suspend fun saveSelectedPaymentMethod(paymentMethod: OnrampPaymentMethod)
|
||||
fun getSelectedPaymentMethod(): Flow<OnrampPaymentMethod>
|
||||
fun getQuotes(): Flow<List<OnrampQuote>>
|
||||
suspend fun getQuotesSync(): List<OnrampQuote>?
|
||||
suspend fun clearCache()
|
||||
}
|
||||
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