Updated on 2026-08-14
This commit is contained in:
commit
d617c9c30d
491 changed files with 15019 additions and 5050 deletions
|
|
@ -708,6 +708,16 @@
|
|||
"networkId": "cyber/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sei-network",
|
||||
"name": "Sei Network",
|
||||
"symbol": "SEI",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "sei-network/test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import com.tangem.core.navigation.url.UrlOpener
|
|||
import com.tangem.datasource.asset.loader.AssetLoader
|
||||
import com.tangem.datasource.config.ConfigManager
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
|
|
@ -76,7 +76,7 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getBalanceHidingRepository(): BalanceHidingRepository
|
||||
|
||||
fun getUserTokensStore(): UserTokensStore
|
||||
fun getAppPreferencesStore(): AppPreferencesStore
|
||||
|
||||
fun getGetAppThemeModeUseCase(): GetAppThemeModeUseCase
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.tangem.datasource.config.ConfigManager
|
|||
import com.tangem.datasource.config.FeaturesLocalLoader
|
||||
import com.tangem.datasource.config.models.Config
|
||||
import com.tangem.datasource.connection.NetworkConnectionManager
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
|
|
@ -126,8 +126,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
private val balanceHidingRepository: BalanceHidingRepository
|
||||
get() = entryPoint.getBalanceHidingRepository()
|
||||
|
||||
private val userTokensStore: UserTokensStore
|
||||
get() = entryPoint.getUserTokensStore()
|
||||
private val appPreferencesStore: AppPreferencesStore
|
||||
get() = entryPoint.getAppPreferencesStore()
|
||||
|
||||
val getAppThemeModeUseCase: GetAppThemeModeUseCase
|
||||
get() = entryPoint.getGetAppThemeModeUseCase()
|
||||
|
|
@ -228,7 +228,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
|
|||
}
|
||||
|
||||
derivationsFinder = DerivationsFinder(
|
||||
newTokensStore = userTokensStore,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = AppCoroutineDispatcherProvider(),
|
||||
)
|
||||
appStateHolder.mainStore = store
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.tap.common.analytics.handlers.firebase
|
||||
|
||||
import com.google.firebase.analytics.ktx.analytics
|
||||
import com.google.firebase.ktx.Firebase
|
||||
import com.tangem.core.analytics.AppInstanceIdProvider
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
internal class FirebaseAppInstanceIdProvider : AppInstanceIdProvider {
|
||||
|
||||
override suspend fun getAppInstanceId(): String? = suspendCancellableCoroutine { continuation ->
|
||||
Firebase.analytics.appInstanceId
|
||||
.addOnSuccessListener { continuation.resume(it) }
|
||||
.addOnFailureListener {
|
||||
Timber.w("Fail to get appInstanceId")
|
||||
continuation.resume(null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAppInstanceIdSync(): String? {
|
||||
return Firebase.analytics.appInstanceId.result
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.tap.di.analytics
|
||||
|
||||
import com.tangem.core.analytics.AppInstanceIdProvider
|
||||
import com.tangem.core.analytics.utils.AnalyticsContextProxy
|
||||
import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase
|
||||
import com.tangem.tap.common.analytics.DefaultAnalyticsContextProxy
|
||||
import com.tangem.tap.common.analytics.DefaultChangeCardAnalyticsContextUseCase
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAppInstanceIdProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -23,4 +25,8 @@ internal object AnalyticsModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideAnalyticsContextProxy(): AnalyticsContextProxy = DefaultAnalyticsContextProxy()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppInstanceIdProvider(): AppInstanceIdProvider = FirebaseAppInstanceIdProvider()
|
||||
}
|
||||
|
|
@ -92,4 +92,12 @@ internal object CardDomainModule {
|
|||
fun provideNetworkHasDerivationUseCase(): NetworkHasDerivationUseCase {
|
||||
return NetworkHasDerivationUseCase()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsRequiredDerivePublicKeysUseCase(
|
||||
derivationsRepository: DerivationsRepository,
|
||||
): HasMissedDerivationsUseCase {
|
||||
return HasMissedDerivationsUseCase(derivationsRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.managetokens.GetManagedTokensUseCase
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.managetokens.*
|
||||
import com.tangem.domain.managetokens.repository.CustomTokensRepository
|
||||
import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -17,4 +19,66 @@ internal object ManageTokensDomainModule {
|
|||
fun provideGetManageTokensUseCase(manageTokensRepository: ManageTokensRepository): GetManagedTokensUseCase {
|
||||
return GetManagedTokensUseCase(manageTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideValidateTokenFormatUseCase(customTokensRepository: CustomTokensRepository): ValidateTokenFormUseCase {
|
||||
return ValidateTokenFormUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCreateCurrencyUseCase(customTokensRepository: CustomTokensRepository): CreateCurrencyUseCase {
|
||||
return CreateCurrencyUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFindTokenUseCase(customTokensRepository: CustomTokensRepository): FindTokenUseCase {
|
||||
return FindTokenUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCheckIsCurrencyNotAddedUseCase(
|
||||
customTokensRepository: CustomTokensRepository,
|
||||
): CheckIsCurrencyNotAddedUseCase {
|
||||
return CheckIsCurrencyNotAddedUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRemoveCustomManagedCryptoCurrencyUseCase(
|
||||
customTokensRepository: CustomTokensRepository,
|
||||
): RemoveCustomManagedCryptoCurrencyUseCase {
|
||||
return RemoveCustomManagedCryptoCurrencyUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSaveManagedTokensUseCase(
|
||||
manageTokensRepository: ManageTokensRepository,
|
||||
derivationsRepository: DerivationsRepository,
|
||||
): SaveManagedTokensUseCase {
|
||||
return SaveManagedTokensUseCase(
|
||||
manageTokensRepository = manageTokensRepository,
|
||||
derivationsRepository = derivationsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetSupportedNetworksUseCase(
|
||||
customTokensRepository: CustomTokensRepository,
|
||||
): GetSupportedNetworksUseCase {
|
||||
return GetSupportedNetworksUseCase(customTokensRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideValidateDerivationPathUseCase(
|
||||
customTokensRepository: CustomTokensRepository,
|
||||
): ValidateDerivationPathUseCase {
|
||||
return ValidateDerivationPathUseCase(customTokensRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
|
||||
import com.tangem.domain.markets.GetTokenMarketInfoUseCase
|
||||
import com.tangem.domain.markets.GetTokenPriceChartUseCase
|
||||
import com.tangem.domain.markets.GetTokenQuotesUseCase
|
||||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -37,7 +35,13 @@ object MarketsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTokenQuotesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenQuotesUseCase {
|
||||
return GetTokenQuotesUseCase(marketsTokenRepository = marketsTokenRepository)
|
||||
fun provideTokenFullQuotesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenFullQuotesUseCase {
|
||||
return GetTokenFullQuotesUseCase(marketsTokenRepository = marketsTokenRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTokenQuotesUseCase(quotesRepository: QuotesRepository): GetCurrencyQuotesUseCase {
|
||||
return GetCurrencyQuotesUseCase(quotesRepository = quotesRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -145,18 +145,6 @@ internal object StakingDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsStakeMoreAvailableUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): IsStakeMoreAvailableUseCase {
|
||||
return IsStakeMoreAvailableUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsApproveNeededUseCase(
|
||||
|
|
|
|||
|
|
@ -104,6 +104,22 @@ internal object TokensDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetAllWalletsCryptoCurrencyStatusesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
): GetAllWalletsCryptoCurrencyStatusesUseCase {
|
||||
return GetAllWalletsCryptoCurrencyStatusesUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCurrencyWarningsUseCase(
|
||||
|
|
@ -158,8 +174,9 @@ internal object TokensDomainModule {
|
|||
currenciesRepository: CurrenciesRepository,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
): FetchCurrencyStatusUseCase {
|
||||
return FetchCurrencyStatusUseCase(currenciesRepository, networksRepository, quotesRepository)
|
||||
return FetchCurrencyStatusUseCase(currenciesRepository, networksRepository, quotesRepository, stakingRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -419,4 +436,10 @@ internal object TokensDomainModule {
|
|||
fun provideCheckHasLinkedTokensUseCase(currenciesRepository: CurrenciesRepository): CheckHasLinkedTokensUseCase {
|
||||
return CheckHasLinkedTokensUseCase(currenciesRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCurrencyCheckUseCase(currencyChecksRepository: CurrencyChecksRepository): GetCurrencyCheckUseCase {
|
||||
return GetCurrencyCheckUseCase(currencyChecksRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,21 @@ internal object TransactionDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSendMultipleTransactionUseCase(
|
||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
transactionRepository: TransactionRepository,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
): SendMultipleTransactionUseCase {
|
||||
return SendMultipleTransactionUseCase(
|
||||
demoConfig = DemoConfig(),
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
transactionRepository = transactionRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAssociateAssetUseCase(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.tap.domain.card
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
|
|
@ -8,10 +10,13 @@ import com.tangem.common.doOnSuccess
|
|||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.card.repository.DerivationsRepository
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
|
@ -30,6 +35,10 @@ internal class DefaultDerivationsRepository(
|
|||
) : DerivationsRepository {
|
||||
|
||||
override suspend fun derivePublicKeys(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
derivePublicKeysByNetworks(userWalletId = userWalletId, networks = currencies.map(CryptoCurrency::network))
|
||||
}
|
||||
|
||||
override suspend fun derivePublicKeysByNetworks(userWalletId: UserWalletId, networks: List<Network>) {
|
||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||
|
||||
if (!userWallet.scanResponse.card.settings.isHDWalletAllowed) {
|
||||
|
|
@ -38,7 +47,7 @@ internal class DefaultDerivationsRepository(
|
|||
}
|
||||
|
||||
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
|
||||
.find(currencies)
|
||||
.findByNetworks(networks)
|
||||
.ifEmpty {
|
||||
Timber.d("Nothing to derive")
|
||||
return
|
||||
|
|
@ -47,6 +56,26 @@ internal class DefaultDerivationsRepository(
|
|||
derivePublicKeys(userWalletId = userWalletId, derivations = derivations)
|
||||
}
|
||||
|
||||
override suspend fun hasMissedDerivations(
|
||||
userWalletId: UserWalletId,
|
||||
networksWithDerivationPath: Map<Network.ID, String?>,
|
||||
): Boolean {
|
||||
val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found")
|
||||
|
||||
val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse)
|
||||
.findByNetworks(
|
||||
networksWithDerivationPath.mapNotNull { (networkId, extraDerivationPath) ->
|
||||
getNetwork(
|
||||
blockchain = Blockchain.fromNetworkId(networkId.value) ?: return@mapNotNull null,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
return derivations.isNotEmpty()
|
||||
}
|
||||
|
||||
override suspend fun derivePublicKeys(userWalletId: UserWalletId, derivations: Derivations): DerivedKeys {
|
||||
tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)
|
||||
.doOnSuccess { response ->
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.common.util.derivationStyleProvider
|
|||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
private typealias DerivationData = Pair<ByteArrayKey, List<DerivationPath>>
|
||||
|
|
@ -26,8 +27,12 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
|||
|
||||
/** Find missed derivations for given currencies [currencies] */
|
||||
fun find(currencies: List<CryptoCurrency>): Derivations {
|
||||
return currencies.map { it.network }.let(::findByNetworks)
|
||||
}
|
||||
|
||||
fun findByNetworks(networks: List<Network>): Derivations {
|
||||
return buildMap<ByteArrayKey, MutableList<DerivationPath>> {
|
||||
currencies
|
||||
networks
|
||||
.mapToNewDerivations()
|
||||
.forEach { data ->
|
||||
val current = this[data.first]
|
||||
|
|
@ -41,25 +46,25 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrency>.mapToNewDerivations(): List<DerivationData> {
|
||||
private fun List<Network>.mapToNewDerivations(): List<DerivationData> {
|
||||
val config = CardConfig.createConfig(scanResponse.card)
|
||||
return mapNotNull { currency ->
|
||||
val blockchain = Blockchain.fromId(id = currency.network.id.value)
|
||||
return mapNotNull { network ->
|
||||
val blockchain = Blockchain.fromId(id = network.id.value)
|
||||
val curve = config.primaryCurve(blockchain) ?: return@mapNotNull null
|
||||
|
||||
findNewDerivations(curve = curve, scanResponse = scanResponse, currency = currency)
|
||||
findNewDerivations(curve = curve, scanResponse = scanResponse, network = network)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findNewDerivations(
|
||||
curve: EllipticCurve,
|
||||
scanResponse: ScanResponse,
|
||||
currency: CryptoCurrency,
|
||||
network: Network,
|
||||
): DerivationData? {
|
||||
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
|
||||
val publicKey = wallet.publicKey.toMapKey()
|
||||
|
||||
val derivationCandidates = currency
|
||||
val derivationCandidates = network
|
||||
.getDerivationCandidates(curve)
|
||||
.ifEmpty { return null }
|
||||
.filterAlreadyDerivedKeys(publicKey)
|
||||
|
|
@ -68,13 +73,13 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
|||
return publicKey to derivationCandidates
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
||||
val blockchain = Blockchain.fromId(id = network.id.value)
|
||||
private fun Network.getDerivationCandidates(curve: EllipticCurve): List<DerivationPath> {
|
||||
val blockchain = Blockchain.fromId(id = this.id.value)
|
||||
|
||||
return buildList {
|
||||
add(blockchain.getDerivationPath(curve = curve))
|
||||
add(blockchain.getCustomDerivationPath(curve = curve, currency = this@getDerivationCandidates))
|
||||
add(blockchain.getCardanoDerivationPathIfNeeded(currency = this@getDerivationCandidates))
|
||||
add(blockchain.getCustomDerivationPath(curve = curve, network = this@getDerivationCandidates))
|
||||
add(blockchain.getCardanoDerivationPathIfNeeded(network = this@getDerivationCandidates))
|
||||
}
|
||||
.filterNotNull()
|
||||
.distinct()
|
||||
|
|
@ -88,17 +93,17 @@ internal class MissedDerivationsFinder(private val scanResponse: ScanResponse) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, currency: CryptoCurrency): DerivationPath? {
|
||||
private fun Blockchain.getCustomDerivationPath(curve: EllipticCurve, network: Network): DerivationPath? {
|
||||
return if (getSupportedCurves().contains(curve)) {
|
||||
currency.network.derivationPath.value?.let(::DerivationPath)
|
||||
network.derivationPath.value?.let(::DerivationPath)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Blockchain.getCardanoDerivationPathIfNeeded(currency: CryptoCurrency): DerivationPath? {
|
||||
return if (currency is CryptoCurrency.Coin && this == Blockchain.Cardano) {
|
||||
currency.network.derivationPath.value?.let {
|
||||
private fun Blockchain.getCardanoDerivationPathIfNeeded(network: Network): DerivationPath? {
|
||||
return if (this == Blockchain.Cardano) {
|
||||
network.derivationPath.value?.let {
|
||||
CardanoUtils.extendedDerivationPath(derivationPath = DerivationPath(it))
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@ object WalletMockContent : MockContent {
|
|||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
),
|
||||
),
|
||||
extendedPublicKey = ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||
|
|
@ -189,6 +193,13 @@ object WalletMockContent : MockContent {
|
|||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/195'/0'/0/0") to ExtendedPublicKey(
|
||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -22,7 +25,7 @@ internal data class BlockchainToDerive(
|
|||
|
||||
// FIXME: May be move to DI, currently unnecessary
|
||||
internal class DerivationsFinder(
|
||||
private val newTokensStore: UserTokensStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
|
|
@ -64,7 +67,9 @@ internal class DerivationsFinder(
|
|||
}
|
||||
|
||||
private suspend fun getBlockchains(userWalletId: UserWalletId): MutableSet<BlockchainToDerive> {
|
||||
val responseTokens = newTokensStore.getSyncOrNull(userWalletId)
|
||||
val responseTokens = appPreferencesStore.getObjectSyncOrNull<UserTokensResponse>(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId.stringValue),
|
||||
)
|
||||
?.tokens
|
||||
?: return hashSetOf()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.details.ui.appsettings.AppSettingsDialogsFactory
|
||||
|
|
@ -20,12 +20,12 @@ internal fun SettingsAlertDialog(dialog: Dialog.Alert) {
|
|||
title = dialog.title.resolveReference(),
|
||||
message = dialog.description.resolveReference(),
|
||||
isDismissable = false,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = dialog.confirmText.resolveReference(),
|
||||
warning = true,
|
||||
onClick = dialog.onConfirm,
|
||||
),
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = dialog.onDismiss,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.components.SelectorDialog
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
|
@ -21,7 +21,7 @@ internal fun SettingsSelectorDialog(dialog: Dialog.Selector) {
|
|||
title = dialog.title.resolveReference(),
|
||||
selectedItemIndex = dialog.selectedItemIndex,
|
||||
items = dialog.items.map { it.resolveReference() }.toImmutableList(),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(R.string.common_cancel),
|
||||
onClick = dialog.onDismiss,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.core.analytics.Analytics
|
|||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.getBackupCardsCount
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
|
|
@ -173,13 +174,7 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
userWalletId = userWalletId,
|
||||
cardId = card.cardId,
|
||||
isActiveBackupStatus = card.backupStatus?.isActive == true,
|
||||
backupCardsCount = when (val status = card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
CardDTO.BackupStatus.NoBackup,
|
||||
null,
|
||||
-> 0
|
||||
},
|
||||
backupCardsCount = scanResponse.getBackupCardsCount() ?: 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,11 +190,11 @@ private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
|
|||
BasicDialog(
|
||||
title = stringResource(dialog.titleResId),
|
||||
message = stringResource(dialog.messageResId),
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_cancel),
|
||||
onClick = dialog.onDismiss,
|
||||
),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.card_settings_action_sheet_reset),
|
||||
warning = true,
|
||||
onClick = dialog.onConfirmClick,
|
||||
|
|
@ -208,7 +208,7 @@ private fun CompletedResetDialog(dialog: ResetCardDialog) {
|
|||
BasicDialog(
|
||||
title = stringResource(id = dialog.titleResId),
|
||||
message = stringResource(id = dialog.messageResId),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = dialog.onConfirmClick,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ internal class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
|||
viewModel.onSearchClick()
|
||||
} else {
|
||||
Analytics.send(IntroductionProcess.ButtonTokensList())
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(readOnlyContent = true)) }
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens()) }
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ internal class HomeViewModel @Inject constructor(
|
|||
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
|
||||
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens(readOnlyContent = true)) }
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens()) }
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.saveWallet.ui.models.EnrollBiometricsDialog
|
||||
|
||||
@Composable
|
||||
|
|
@ -19,11 +19,11 @@ fun EnrollBiometricsDialogContent(dialog: EnrollBiometricsDialog) {
|
|||
BasicDialog(
|
||||
title = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_title),
|
||||
message = stringResource(R.string.save_user_wallet_agreement_enroll_biometrics_description),
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(R.string.common_enable),
|
||||
onClick = dialog.onEnroll,
|
||||
),
|
||||
dismissButton = DialogButton(
|
||||
dismissButton = DialogButtonUM(
|
||||
onClick = dialog.onCancel,
|
||||
),
|
||||
onDismissDialog = dialog.onCancel,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
|
|
@ -48,12 +47,6 @@ object TradeCryptoMiddleware {
|
|||
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
||||
is TradeCryptoAction.Sell -> proceedSellAction(action)
|
||||
is TradeCryptoAction.Swap -> openSwap(currency = action.cryptoCurrency)
|
||||
is TradeCryptoAction.Stake -> openStaking(
|
||||
userWalletId = action.userWalletId,
|
||||
cryptoCurrencyId = action.cryptoCurrencyId,
|
||||
yield = action.yield,
|
||||
)
|
||||
is TradeCryptoAction.SendToken -> handleNewSendToken(action = action)
|
||||
is TradeCryptoAction.SendCoin -> handleNewSendCoin(action = action)
|
||||
}
|
||||
|
|
@ -141,22 +134,6 @@ object TradeCryptoMiddleware {
|
|||
)?.let { store.dispatchOpenUrl(it) }
|
||||
}
|
||||
|
||||
private fun openSwap(currency: CryptoCurrency) {
|
||||
store.dispatchNavigationAction { push(AppRoute.Swap(currency = currency)) }
|
||||
}
|
||||
|
||||
private fun openStaking(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, yield: Yield) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Staking(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrencyId,
|
||||
yield = yield,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewSendToken(action: TradeCryptoAction.SendToken) {
|
||||
handleNewSend(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButton
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -27,7 +27,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
|||
},
|
||||
),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
|
|
@ -38,7 +38,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
|||
title = stringResource(id = R.string.common_attention),
|
||||
message = stringResource(id = R.string.key_invalidated_warning_description),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
|
|
@ -50,7 +50,7 @@ internal fun WarningDialog(warning: WarningModel?) {
|
|||
message = stringResource(id = R.string.biometric_unavailable_warning),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
isDismissable = false,
|
||||
confirmButton = DialogButton(
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResource(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.details.component.DetailsComponent
|
|||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||
import com.tangem.features.managetokens.ManageTokensToggles
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
|
|
@ -50,6 +51,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
|
||||
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
|
||||
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
|
||||
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
|
||||
private val sendRouter: SendRouter,
|
||||
private val tokenDetailsRouter: TokenDetailsRouter,
|
||||
private val walletRouter: WalletRouter,
|
||||
|
|
@ -126,13 +128,7 @@ internal class ChildFactory @Inject constructor(
|
|||
if (manageTokensToggles.isFeatureEnabled) {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = ManageTokensComponent.Params(
|
||||
mode = if (route.readOnlyContent) {
|
||||
ManageTokensComponent.Mode.READ_ONLY
|
||||
} else {
|
||||
ManageTokensComponent.Mode.MANAGE
|
||||
},
|
||||
),
|
||||
params = ManageTokensComponent.Params(route.userWalletId),
|
||||
componentFactory = manageTokensComponentFactory,
|
||||
)
|
||||
} else {
|
||||
|
|
@ -191,6 +187,16 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = walletSettingsComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.MarketsTokenDetails -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = MarketsTokenDetailsComponent.Params(
|
||||
token = route.token,
|
||||
appCurrency = route.appCurrency,
|
||||
),
|
||||
componentFactory = marketsTokenDetailsComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,84 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/llTotalContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTotal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:text="@string/send_total_label"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="usd" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvWillBeSentValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/text_tertiary"
|
||||
tools:text="123.29837729 ADA 39487593.109342039402938049 will be sent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/flTotalTokenCrypto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalTokenCrypto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:text="@string/send_total_label"
|
||||
android:textColor="@color/text_tertiary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalTokenCryptoValue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/text_tertiary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="usd" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
package com.tangem.tap.domain.card
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.FeePaidCurrency
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.getNetworkDerivationPath
|
||||
import com.tangem.data.common.currency.getNetworkStandardType
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -40,11 +44,23 @@ internal class CryptoCurrenciesMocks(private val scanResponse: ScanResponse) {
|
|||
}
|
||||
|
||||
private fun createCoin(blockchain: Blockchain): CryptoCurrency {
|
||||
return factory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
derivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
)!!
|
||||
val network = Network(
|
||||
id = Network.ID(blockchain.id),
|
||||
backendId = blockchain.toNetworkId(),
|
||||
name = blockchain.getNetworkName(),
|
||||
isTestnet = blockchain.isTestnet(),
|
||||
derivationPath = getNetworkDerivationPath(
|
||||
blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse.derivationStyleProvider,
|
||||
),
|
||||
currencySymbol = blockchain.currency,
|
||||
standardType = getNetworkStandardType(blockchain),
|
||||
hasFiatFeeRate = blockchain.feePaidCurrency() !is FeePaidCurrency.FeeResource,
|
||||
canHandleTokens = false,
|
||||
)
|
||||
|
||||
return factory.createCoin(network = network)
|
||||
}
|
||||
|
||||
// Impossible to create custom token by CryptoCurrencyFactory because it works with URI under the hood
|
||||
|
|
@ -68,6 +84,7 @@ internal class CryptoCurrenciesMocks(private val scanResponse: ScanResponse) {
|
|||
isTestnet = false,
|
||||
standardType = Network.StandardType.ERC20,
|
||||
hasFiatFeeRate = true,
|
||||
canHandleTokens = true,
|
||||
),
|
||||
name = "NEVER-MIND",
|
||||
symbol = "NEVER-MIND",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ dependencies {
|
|||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.staking.models)
|
||||
implementation(projects.domain.markets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
|
||||
/* Libs - Other */
|
||||
api(deps.kotlin.serialization)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.tangem.common.routing.bundle.RouteBundleParams
|
|||
import com.tangem.common.routing.bundle.bundle
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -177,8 +179,8 @@ sealed class AppRoute(val path: String) : Route {
|
|||
|
||||
@Serializable
|
||||
data class ManageTokens(
|
||||
val readOnlyContent: Boolean,
|
||||
) : AppRoute(path = "/manage_tokens/$readOnlyContent"), RouteBundleParams {
|
||||
val userWalletId: UserWalletId? = null,
|
||||
) : AppRoute(path = "/manage_tokens/$userWalletId"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
|
||||
|
|
@ -217,12 +219,14 @@ sealed class AppRoute(val path: String) : Route {
|
|||
@Serializable
|
||||
data class Swap(
|
||||
val currency: CryptoCurrency,
|
||||
) : AppRoute(path = "/swap/${currency.id.value}"), RouteBundleParams {
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/swap/${currency.id.value}/${userWalletId.stringValue}"), RouteBundleParams {
|
||||
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
|
||||
companion object {
|
||||
const val CURRENCY_BUNDLE_KEY = "currency"
|
||||
const val USER_WALLET_ID_KEY = "userWalletId"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -262,4 +266,10 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data class WalletSettings(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/wallet_settings/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class MarketsTokenDetails(
|
||||
val token: TokenMarketParams,
|
||||
val appCurrency: AppCurrency,
|
||||
) : AppRoute(path = "/markets_token_details/${token.id}")
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.common.routing.utils
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Temporary solution to convert [AppRouter] to [Router].
|
||||
|
||||
* (through manual ComponentContext creation).
|
||||
*
|
||||
* **Will be removed when all screens will be migrated to Decompose.**
|
||||
*
|
||||
* @return [Router] that wraps [AppRouter].
|
||||
*/
|
||||
fun AppRouter.asRouter(): Router {
|
||||
return RouterProxy(appRouter = this)
|
||||
}
|
||||
|
||||
private class RouterProxy(
|
||||
private val appRouter: AppRouter,
|
||||
) : Router {
|
||||
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
if (route is AppRoute) {
|
||||
appRouter.push(route, onComplete)
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
routes.filterIsInstance<AppRoute>().let {
|
||||
appRouter.replaceAll(*it.toTypedArray(), onComplete = onComplete)
|
||||
}
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
appRouter.pop(onComplete)
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
if (route is AppRoute) {
|
||||
appRouter.popTo(route, onComplete)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
appRouter.popTo(routeClass as KClass<out AppRoute>, onComplete)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.common.ui.charts.state
|
||||
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
fun MarketChartData.Data.sorted(): MarketChartData.Data {
|
||||
val points = this.x.zip(this.y).sortedBy { it.first }
|
||||
val (x, y) = points.unzip()
|
||||
|
||||
return MarketChartData.Data(
|
||||
x = x.toImmutableList(),
|
||||
y = y.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ dependencies {
|
|||
implementation(deps.compose.ui.tooling)
|
||||
implementation(deps.compose.navigation)
|
||||
implementation(deps.compose.navigation.hilt)
|
||||
implementation(deps.compose.coil)
|
||||
|
||||
/** Deps */
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
|
|
@ -27,9 +28,14 @@ dependencies {
|
|||
implementation(projects.core.utils)
|
||||
|
||||
/** Project - Domain */
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.staking.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.common.ui.alerts
|
||||
|
||||
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
|
||||
import com.tangem.common.ui.alerts.models.AlertTransactionErrorUM
|
||||
import com.tangem.common.ui.alerts.models.AlertUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.transaction.error.SendTransactionError
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
class SendTransactionAlertConverter(
|
||||
private val popBackStack: () -> Unit,
|
||||
private val onFailedTxEmailClick: (String) -> Unit,
|
||||
) : Converter<SendTransactionError, AlertUM?> {
|
||||
override fun convert(value: SendTransactionError): AlertUM? {
|
||||
return when (value) {
|
||||
is SendTransactionError.DemoCardError -> AlertDemoModeUM(
|
||||
onConfirmClick = popBackStack,
|
||||
)
|
||||
is SendTransactionError.TangemSdkError -> AlertTransactionErrorUM(
|
||||
code = value.code.toString(),
|
||||
cause = null,
|
||||
causeTextReference = resourceReference(value.messageRes, wrappedList(value.args)),
|
||||
onConfirmClick = { onFailedTxEmailClick(value.code.toString()) },
|
||||
)
|
||||
is SendTransactionError.BlockchainSdkError -> AlertTransactionErrorUM(
|
||||
code = value.code.toString(),
|
||||
cause = value.message,
|
||||
onConfirmClick = { onFailedTxEmailClick("${value.code}: ${value.message.orEmpty()}") },
|
||||
)
|
||||
is SendTransactionError.DataError -> AlertTransactionErrorUM(
|
||||
code = "",
|
||||
cause = value.message,
|
||||
onConfirmClick = { onFailedTxEmailClick(value.message.orEmpty()) },
|
||||
)
|
||||
is SendTransactionError.NetworkError -> AlertTransactionErrorUM(
|
||||
code = value.code.orEmpty(),
|
||||
cause = value.message.orEmpty(),
|
||||
onConfirmClick = { onFailedTxEmailClick(value.message.orEmpty()) },
|
||||
)
|
||||
is SendTransactionError.UnknownError -> AlertTransactionErrorUM(
|
||||
code = "",
|
||||
cause = value.ex?.localizedMessage,
|
||||
onConfirmClick = { onFailedTxEmailClick(value.ex?.localizedMessage.orEmpty()) },
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.common.ui.alerts.models
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
|
||||
data class AlertDemoModeUM(
|
||||
override val onConfirmClick: () -> Unit,
|
||||
) : AlertUM {
|
||||
override val confirmButtonText: TextReference = resourceReference(id = R.string.common_ok)
|
||||
override val title: TextReference = resourceReference(id = R.string.warning_demo_mode_title)
|
||||
override val message: TextReference = resourceReference(id = R.string.warning_demo_mode_message)
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.common.ui.alerts.models
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
|
||||
data class AlertTransactionErrorUM(
|
||||
val code: String,
|
||||
val cause: String?,
|
||||
val causeTextReference: TextReference? = null,
|
||||
override val onConfirmClick: (() -> Unit)? = null,
|
||||
) : AlertUM {
|
||||
override val title: TextReference = resourceReference(id = R.string.send_alert_transaction_failed_title)
|
||||
override val message: TextReference = resourceReference(
|
||||
id = R.string.send_alert_transaction_failed_text,
|
||||
formatArgs = wrappedList(causeTextReference ?: cause.orEmpty(), code),
|
||||
)
|
||||
override val confirmButtonText: TextReference =
|
||||
resourceReference(id = R.string.common_support)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.common.ui.alerts.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
@Immutable
|
||||
interface AlertUM {
|
||||
val title: TextReference?
|
||||
val message: TextReference
|
||||
val confirmButtonText: TextReference
|
||||
val onConfirmClick: (() -> Unit)?
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.common.ui.amountScreen.converters.field
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
|
|
@ -81,6 +82,10 @@ class AmountFieldChangeTransformer(
|
|||
cryptoAmount = amountTextField.cryptoAmount.copy(value = BigDecimal.ZERO),
|
||||
fiatAmount = amountTextField.fiatAmount.copy(value = BigDecimal.ZERO),
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.None,
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ fun GiveTxPermissionBottomSheet(config: TangemBottomSheetConfig) {
|
|||
BasicDialog(
|
||||
message = content.data.dialogText.resolveReference(),
|
||||
title = stringResource(id = R.string.common_approve),
|
||||
confirmButton = DialogButton { isPermissionAlertShow = false },
|
||||
confirmButton = DialogButtonUM { isPermissionAlertShow = false },
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ import androidx.compose.foundation.clickable
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
|
@ -44,25 +43,15 @@ fun NavigationButtonsBlock(buttonState: NavigationButtonsState, modifier: Modifi
|
|||
PreviousButton(state?.prevButton)
|
||||
PrimaryButton(state?.primaryButton, modifier = Modifier.weight(1f))
|
||||
}
|
||||
|
||||
SecondaryButton(state?.secondaryButton)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrimaryButton(primaryButton: NavigationButton?, modifier: Modifier = Modifier) {
|
||||
val wrappedButton by rememberNavigationButton(primaryButton)
|
||||
AnimatedContent(
|
||||
targetState = primaryButton,
|
||||
transitionSpec = {
|
||||
val isPrimaryToHide = targetState != null && initialState == null
|
||||
val isPrimaryWasVisible = targetState == null && initialState != null
|
||||
if (isPrimaryToHide || isPrimaryWasVisible) {
|
||||
slideInVertically(initialOffsetY = { it / 2 }).plus(fadeIn())
|
||||
.togetherWith(slideOutVertically(targetOffsetY = { it / 2 }).plus(fadeOut()))
|
||||
} else {
|
||||
fadeIn().togetherWith(fadeOut())
|
||||
}
|
||||
},
|
||||
targetState = wrappedButton,
|
||||
transitionSpec = { navigationButtonsTransition() },
|
||||
contentAlignment = Alignment.Center,
|
||||
label = "Animate show primary button",
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
|
|
@ -88,43 +77,6 @@ private fun PrimaryButton(primaryButton: NavigationButton?, modifier: Modifier =
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SecondaryButton(secondaryButton: NavigationButton?) {
|
||||
AnimatedContent(
|
||||
targetState = secondaryButton,
|
||||
transitionSpec = {
|
||||
val isPrimaryToHide = targetState != null && initialState == null
|
||||
val isPrimaryWasVisible = targetState == null && initialState != null
|
||||
if (isPrimaryToHide || isPrimaryWasVisible) {
|
||||
slideInVertically(initialOffsetY = { it / 2 }).plus(fadeIn())
|
||||
.togetherWith(slideOutVertically(targetOffsetY = { it / 2 }).plus(fadeOut()))
|
||||
} else {
|
||||
fadeIn().togetherWith(fadeOut())
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.Center,
|
||||
label = "Animate show secondary button",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { button ->
|
||||
if (button != null && button.textReference != TextReference.EMPTY) {
|
||||
val icon = button.iconRes?.let { TangemButtonIconPosition.End(iconResId = it) }
|
||||
?: TangemButtonIconPosition.None
|
||||
|
||||
TangemButton(
|
||||
text = button.textReference.resolveReference(),
|
||||
enabled = button.isEnabled,
|
||||
onClick = button.onClick,
|
||||
icon = icon,
|
||||
showProgress = button.showProgress,
|
||||
colors = TangemButtonsDefaults.secondaryButtonColors,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
} else {
|
||||
Spacer(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PreviousButton(prevButton: NavigationButton?) {
|
||||
AnimatedVisibility(
|
||||
|
|
@ -182,6 +134,28 @@ private fun ExtraButtons(extraButtons: ImmutableList<NavigationButton>?, txUrl:
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberNavigationButton(button: NavigationButton?): MutableState<NavigationButton?> {
|
||||
return remember(
|
||||
button?.iconRes,
|
||||
button?.isIconVisible,
|
||||
button?.isEnabled,
|
||||
button?.showProgress,
|
||||
button?.textReference,
|
||||
) { mutableStateOf(button) }
|
||||
}
|
||||
|
||||
private fun <T> AnimatedContentTransitionScope<T>.navigationButtonsTransition(): ContentTransform {
|
||||
val isPrimaryToHide = targetState != null && initialState == null
|
||||
val isPrimaryWasVisible = targetState == null && initialState != null
|
||||
return if (isPrimaryToHide || isPrimaryWasVisible) {
|
||||
slideInVertically(initialOffsetY = { it / 2 }).plus(fadeIn())
|
||||
.togetherWith(slideOutVertically(targetOffsetY = { it / 2 }).plus(fadeOut()))
|
||||
} else {
|
||||
fadeIn().togetherWith(fadeOut())
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ sealed class NavigationButtonsState {
|
|||
data class Data(
|
||||
val primaryButton: NavigationButton,
|
||||
val prevButton: NavigationButton?,
|
||||
val secondaryButton: NavigationButton?,
|
||||
val extraButtons: ImmutableList<NavigationButton>,
|
||||
val txUrl: String? = null,
|
||||
) : NavigationButtonsState()
|
||||
|
|
|
|||
|
|
@ -30,14 +30,6 @@ internal object NavigationButtonsPreview {
|
|||
),
|
||||
)
|
||||
|
||||
private val next = NavigationButton(
|
||||
textReference = resourceReference(R.string.common_next),
|
||||
isSecondary = false,
|
||||
isIconVisible = false,
|
||||
showProgress = false,
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
)
|
||||
private val prev = NavigationButton(
|
||||
textReference = TextReference.EMPTY,
|
||||
iconRes = R.drawable.ic_back_24,
|
||||
|
|
@ -60,7 +52,6 @@ internal object NavigationButtonsPreview {
|
|||
val allButtons = NavigationButtonsState.Data(
|
||||
primaryButton = finished,
|
||||
prevButton = prev,
|
||||
secondaryButton = next,
|
||||
extraButtons = extraButtons,
|
||||
txUrl = "https://tangem.com",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,251 @@
|
|||
package com.tangem.common.ui.notifications
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import java.math.BigDecimal
|
||||
|
||||
sealed class NotificationUM(val config: NotificationConfig) {
|
||||
|
||||
open class Error(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_24,
|
||||
buttonState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonState,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) {
|
||||
|
||||
data object TotalExceedsBalance : Error(
|
||||
title = resourceReference(R.string.send_notification_exceed_balance_title),
|
||||
subtitle = resourceReference(R.string.send_notification_exceed_balance_text),
|
||||
)
|
||||
|
||||
data object InvalidAmount : Error(
|
||||
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||
subtitle = resourceReference(R.string.send_notification_invalid_amount_text),
|
||||
)
|
||||
|
||||
data class MinimumAmountError(val amount: String) : Error(
|
||||
title = resourceReference(R.string.send_notification_invalid_amount_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_notification_invalid_minimum_amount_text,
|
||||
wrappedList(amount, amount),
|
||||
),
|
||||
)
|
||||
|
||||
data class TransactionLimitError(
|
||||
val cryptoCurrency: String,
|
||||
val utxoLimit: String,
|
||||
val amountLimit: String,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.send_notification_transaction_limit_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_notification_transaction_limit_text,
|
||||
wrappedList(cryptoCurrency, utxoLimit, amountLimit),
|
||||
),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_leave_button, wrappedList(amountLimit)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class ExceedsBalance(
|
||||
val networkIconId: Int,
|
||||
val currencyName: String,
|
||||
val feeName: String,
|
||||
val feeSymbol: String,
|
||||
val networkName: String,
|
||||
val mergeFeeNetworkName: Boolean = false,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_send_blocked_funds_for_fee_title,
|
||||
wrappedList(feeName),
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.warning_send_blocked_funds_for_fee_message,
|
||||
formatArgs = wrappedList(currencyName, networkName, currencyName, feeName, feeSymbol),
|
||||
),
|
||||
iconResId = networkIconId,
|
||||
buttonState = onClick?.let {
|
||||
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(
|
||||
R.string.common_buy_currency,
|
||||
wrappedList(
|
||||
if (mergeFeeNetworkName) {
|
||||
"$currencyName ($feeSymbol)"
|
||||
} else {
|
||||
feeName
|
||||
},
|
||||
),
|
||||
),
|
||||
onClick = onClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
data class ExistentialDeposit(val deposit: String, val onConfirmClick: () -> Unit) : Error(
|
||||
title = resourceReference(R.string.send_notification_existential_deposit_title),
|
||||
subtitle = resourceReference(R.string.send_notification_existential_deposit_text, wrappedList(deposit)),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_leave_button, wrappedList(deposit)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
|
||||
data class ReserveAmount(val amount: String) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.send_notification_invalid_reserve_amount_title,
|
||||
wrappedList(amount),
|
||||
),
|
||||
subtitle = resourceReference(id = R.string.send_notification_invalid_reserve_amount_text),
|
||||
)
|
||||
}
|
||||
|
||||
open class Warning(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.img_attention_20,
|
||||
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM(
|
||||
config = NotificationConfig(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonsState,
|
||||
onCloseClick = onCloseClick,
|
||||
),
|
||||
) {
|
||||
data class HighFeeError(
|
||||
val currencyName: String,
|
||||
val amount: String,
|
||||
val onConfirmClick: () -> Unit,
|
||||
val onCloseClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = resourceReference(R.string.send_notification_high_fee_title),
|
||||
subtitle = resourceReference(R.string.send_notification_high_fee_text, wrappedList(currencyName, amount)),
|
||||
buttonsState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_reduce_by, wrappedList(amount)),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
onCloseClick = onCloseClick,
|
||||
)
|
||||
|
||||
data object FeeTooLow : Warning(
|
||||
title = resourceReference(id = R.string.send_notification_transaction_delay_title),
|
||||
subtitle = resourceReference(id = R.string.send_notification_transaction_delay_text),
|
||||
)
|
||||
|
||||
data class TooHigh(
|
||||
val value: String,
|
||||
) : Warning(
|
||||
title = resourceReference(id = R.string.send_notification_fee_too_high_title),
|
||||
subtitle = resourceReference(id = R.string.send_notification_fee_too_high_text, wrappedList(value)),
|
||||
)
|
||||
|
||||
data class NetworkFeeUnreachable(val onRefresh: () -> Unit) : Warning(
|
||||
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||
subtitle = resourceReference(R.string.send_fee_unreachable_error_text),
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = onRefresh,
|
||||
),
|
||||
)
|
||||
|
||||
data class TronAccountNotActivated(val tokenName: String) : Warning(
|
||||
title = resourceReference(R.string.send_fee_unreachable_error_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.send_tron_account_activation_error,
|
||||
wrappedList(tokenName),
|
||||
),
|
||||
)
|
||||
|
||||
data class FeeCoverageNotification(val cryptoAmount: String, val fiatAmount: String) : Warning(
|
||||
title = resourceReference(R.string.send_network_fee_warning_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.common_network_fee_warning_content,
|
||||
wrappedList(cryptoAmount, fiatAmount),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Cardano {
|
||||
|
||||
data class MinAdaValueCharged(val tokenName: String, val minAdaValue: String) : Warning(
|
||||
title = resourceReference(id = R.string.cardano_coin_will_be_send_with_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_coin_will_be_send_with_token_description,
|
||||
formatArgs = wrappedList(minAdaValue, tokenName),
|
||||
),
|
||||
)
|
||||
|
||||
data object InsufficientBalanceToTransferCoin : Error(
|
||||
title = resourceReference(id = R.string.cardano_max_amount_has_token_title),
|
||||
subtitle = resourceReference(id = R.string.cardano_max_amount_has_token_description),
|
||||
)
|
||||
|
||||
data class InsufficientBalanceToTransferToken(val tokenName: String) : Error(
|
||||
title = resourceReference(id = R.string.cardano_insufficient_balance_to_send_token_title),
|
||||
subtitle = resourceReference(
|
||||
id = R.string.cardano_insufficient_balance_to_send_token_description,
|
||||
formatArgs = wrappedList(tokenName),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface Koinos {
|
||||
data class InsufficientRecoverableMana(
|
||||
val mana: BigDecimal,
|
||||
val maxMana: BigDecimal,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.koinos_insufficient_mana_to_send_koin_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.koinos_insufficient_mana_to_send_koin_description,
|
||||
formatArgs = wrappedList(
|
||||
BigDecimalFormatter.formatCryptoAmountShorted(mana, "", Blockchain.Koinos.decimals()),
|
||||
BigDecimalFormatter.formatCryptoAmountShorted(maxMana, "", Blockchain.Koinos.decimals()),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
data object InsufficientBalance : Error(
|
||||
title = resourceReference(R.string.koinos_insufficient_balance_to_send_koin_title),
|
||||
subtitle = resourceReference(R.string.koinos_insufficient_balance_to_send_koin_description),
|
||||
)
|
||||
|
||||
data class ManaExceedsBalance(
|
||||
val availableKoinForTransfer: BigDecimal,
|
||||
val onReduceClick: () -> Unit,
|
||||
) : Error(
|
||||
title = resourceReference(R.string.koinos_mana_exceeds_koin_balance_title),
|
||||
subtitle = resourceReference(
|
||||
R.string.koinos_mana_exceeds_koin_balance_description,
|
||||
formatArgs = wrappedList(
|
||||
BigDecimalFormatter.formatCryptoAmount(
|
||||
availableKoinForTransfer,
|
||||
Blockchain.Koinos.currency,
|
||||
Blockchain.Koinos.decimals(),
|
||||
),
|
||||
),
|
||||
),
|
||||
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
|
||||
text = resourceReference(R.string.send_notification_reduce_to, wrappedList(availableKoinForTransfer)),
|
||||
onClick = onReduceClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
package com.tangem.common.ui.notifications
|
||||
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
|
||||
import com.tangem.common.ui.amountScreen.utils.getFiatString
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.transaction.error.GetFeeError
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("LargeClass")
|
||||
object NotificationsFactory {
|
||||
|
||||
fun MutableList<NotificationUM>.addFeeUnreachableNotification(
|
||||
feeError: GetFeeError,
|
||||
tokenName: String,
|
||||
onReload: () -> Unit,
|
||||
) {
|
||||
when (feeError) {
|
||||
is GetFeeError.BlockchainErrors.TronActivationError -> add(
|
||||
NotificationUM.Warning.TronAccountNotActivated(tokenName),
|
||||
)
|
||||
is GetFeeError.DataError,
|
||||
is GetFeeError.UnknownError,
|
||||
-> add(
|
||||
NotificationUM.Warning.NetworkFeeUnreachable(onReload),
|
||||
)
|
||||
else -> {
|
||||
/* do nothing */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addExceedBalanceNotification(
|
||||
feeAmount: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
isSubtractionAvailable: Boolean,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
|
||||
if (!isSubtractionAvailable) return
|
||||
|
||||
val showNotification = sendingAmount + feeAmount > balance
|
||||
if (showNotification) {
|
||||
add(NotificationUM.Error.TotalExceedsBalance)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addReserveAmountErrorNotification(
|
||||
reserveAmount: BigDecimal?,
|
||||
sendingAmount: BigDecimal,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
isAccountFunded: Boolean,
|
||||
) {
|
||||
if (!isAccountFunded && reserveAmount != null && reserveAmount > sendingAmount) {
|
||||
add(
|
||||
NotificationUM.Error.ReserveAmount(
|
||||
BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = sendingAmount,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addTransactionLimitErrorNotification(
|
||||
utxoLimit: UtxoAmountLimit?,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
onReduceClick: (
|
||||
reduceAmountTo: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) -> Unit,
|
||||
) {
|
||||
if (utxoLimit != null) {
|
||||
add(
|
||||
NotificationUM.Error.TransactionLimitError(
|
||||
cryptoCurrency = cryptoCurrency.name,
|
||||
utxoLimit = utxoLimit.maxLimit.toPlainString(),
|
||||
amountLimit = BigDecimalFormatter.formatCryptoAmount(
|
||||
cryptoAmount = utxoLimit.maxAmount,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
onConfirmClick = {
|
||||
onReduceClick(
|
||||
utxoLimit.maxAmount,
|
||||
NotificationUM.Error.TransactionLimitError::class.java,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addExistentialWarningNotification(
|
||||
existentialDeposit: BigDecimal?,
|
||||
feeAmount: BigDecimal,
|
||||
receivedAmount: BigDecimal,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
onReduceClick: (
|
||||
reduceAmountBy: BigDecimal,
|
||||
reduceAmountByDiff: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) -> Unit,
|
||||
) {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: return
|
||||
val spendingAmount = if (cryptoCurrency is CryptoCurrency.Token) {
|
||||
feeAmount
|
||||
} else {
|
||||
receivedAmount
|
||||
}
|
||||
val diff = balance.minus(spendingAmount)
|
||||
if (existentialDeposit != null && diff >= BigDecimal.ZERO && existentialDeposit > diff) {
|
||||
add(
|
||||
NotificationUM.Error.ExistentialDeposit(
|
||||
deposit = BigDecimalFormatter.formatCryptoAmountUncapped(
|
||||
cryptoAmount = existentialDeposit,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
onConfirmClick = {
|
||||
onReduceClick(
|
||||
existentialDeposit,
|
||||
existentialDeposit.minus(diff),
|
||||
NotificationUM.Error.ExistentialDeposit::class.java,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addFeeCoverageNotification(
|
||||
isFeeCoverage: Boolean,
|
||||
amountField: AmountFieldModel,
|
||||
sendingValue: BigDecimal,
|
||||
appCurrency: AppCurrency,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
) {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val fiatRate = cryptoCurrencyStatus.value.fiatRate
|
||||
val amountValue = amountField.cryptoAmount.value ?: return
|
||||
|
||||
val cryptoDiff = amountValue.minus(sendingValue)
|
||||
if (isFeeCoverage) {
|
||||
add(
|
||||
NotificationUM.Warning.FeeCoverageNotification(
|
||||
cryptoAmount = BigDecimalFormatter.formatCryptoAmountUncapped(
|
||||
cryptoAmount = cryptoDiff,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
fiatAmount = getFiatString(
|
||||
value = cryptoDiff,
|
||||
rate = fiatRate,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addDustWarningNotification(
|
||||
dustValue: BigDecimal?,
|
||||
feeValue: BigDecimal,
|
||||
sendingAmount: BigDecimal,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||
) {
|
||||
if (dustValue == null) return
|
||||
val isExceedsLimit = checkDustLimits(
|
||||
feeAmount = feeValue,
|
||||
receivedAmount = sendingAmount,
|
||||
dustValue = dustValue,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
feeCurrencyStatus = feeCurrencyStatus,
|
||||
)
|
||||
if (isExceedsLimit) {
|
||||
add(
|
||||
NotificationUM.Error.MinimumAmountError(
|
||||
amount = dustValue.parseBigDecimal(cryptoCurrencyStatus.currency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addExceedsBalanceNotification(
|
||||
cryptoCurrencyWarning: CryptoCurrencyWarning?,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
shouldMergeFeeNetworkName: Boolean,
|
||||
onClick: (CryptoCurrency) -> Unit,
|
||||
onAnalyticsEvent: (CryptoCurrency) -> Unit,
|
||||
) {
|
||||
when (cryptoCurrencyWarning) {
|
||||
is CryptoCurrencyWarning.BalanceNotEnoughForFee -> {
|
||||
add(
|
||||
NotificationUM.Error.ExceedsBalance(
|
||||
networkIconId = cryptoCurrencyWarning.coinCurrency.networkIconResId,
|
||||
networkName = cryptoCurrencyWarning.coinCurrency.name,
|
||||
currencyName = cryptoCurrencyStatus.currency.name,
|
||||
feeName = cryptoCurrencyWarning.coinCurrency.name,
|
||||
feeSymbol = cryptoCurrencyWarning.coinCurrency.symbol,
|
||||
mergeFeeNetworkName = shouldMergeFeeNetworkName,
|
||||
onClick = {
|
||||
onClick(cryptoCurrencyWarning.coinCurrency)
|
||||
},
|
||||
),
|
||||
)
|
||||
onAnalyticsEvent(cryptoCurrencyStatus.currency)
|
||||
}
|
||||
is CryptoCurrencyWarning.CustomTokenNotEnoughForFee -> {
|
||||
val currency = cryptoCurrencyWarning.feeCurrency
|
||||
add(
|
||||
NotificationUM.Error.ExceedsBalance(
|
||||
networkIconId = currency?.networkIconResId ?: R.drawable.ic_alert_24,
|
||||
currencyName = cryptoCurrencyWarning.currency.name,
|
||||
feeName = cryptoCurrencyWarning.feeCurrencyName,
|
||||
feeSymbol = cryptoCurrencyWarning.feeCurrencySymbol,
|
||||
networkName = cryptoCurrencyWarning.networkName,
|
||||
mergeFeeNetworkName = shouldMergeFeeNetworkName,
|
||||
onClick = {
|
||||
currency?.let {
|
||||
onClick(currency)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
onAnalyticsEvent(cryptoCurrencyWarning.currency)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addValidateTransactionNotifications(
|
||||
dustValue: BigDecimal,
|
||||
fee: Fee?,
|
||||
validationError: Throwable?,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
onReduceClick: (
|
||||
reduceAmountTo: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) -> Unit,
|
||||
) {
|
||||
when (validationError) {
|
||||
is BlockchainSdkError.Cardano -> addCardanoTransactionValidationError(
|
||||
error = validationError,
|
||||
sendingCurrency = cryptoCurrency,
|
||||
dustValue = dustValue,
|
||||
)
|
||||
is BlockchainSdkError.Koinos -> addKoinosTransactionValidationError(
|
||||
error = validationError,
|
||||
onReduceClick = onReduceClick,
|
||||
)
|
||||
null -> (fee as? Fee.CardanoToken)?.let {
|
||||
add(
|
||||
NotificationUM.Cardano.MinAdaValueCharged(
|
||||
tokenName = cryptoCurrency.name,
|
||||
minAdaValue = it.minAdaValue.parseBigDecimal(cryptoCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addCardanoTransactionValidationError(
|
||||
error: BlockchainSdkError.Cardano,
|
||||
sendingCurrency: CryptoCurrency,
|
||||
dustValue: BigDecimal?,
|
||||
) {
|
||||
when (error) {
|
||||
BlockchainSdkError.Cardano.InsufficientMinAdaBalanceToSendToken -> {
|
||||
add(NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name))
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalanceToWithdrawTokens -> {
|
||||
when (sendingCurrency) {
|
||||
is CryptoCurrency.Coin -> NotificationUM.Cardano.InsufficientBalanceToTransferCoin
|
||||
is CryptoCurrency.Token -> {
|
||||
NotificationUM.Cardano.InsufficientBalanceToTransferToken(sendingCurrency.name)
|
||||
}
|
||||
}.let(::add)
|
||||
}
|
||||
BlockchainSdkError.Cardano.InsufficientRemainingBalance,
|
||||
BlockchainSdkError.Cardano.InsufficientSendingAdaAmount,
|
||||
-> {
|
||||
dustValue?.let {
|
||||
add(
|
||||
NotificationUM.Error.MinimumAmountError(
|
||||
amount = it.parseBigDecimal(sendingCurrency.decimals),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<NotificationUM>.addKoinosTransactionValidationError(
|
||||
error: BlockchainSdkError.Koinos,
|
||||
onReduceClick: (
|
||||
reduceAmountTo: BigDecimal,
|
||||
notification: Class<out NotificationUM>,
|
||||
) -> Unit,
|
||||
) {
|
||||
when (error) {
|
||||
is BlockchainSdkError.Koinos.InsufficientBalance -> {
|
||||
add(NotificationUM.Koinos.InsufficientBalance)
|
||||
}
|
||||
is BlockchainSdkError.Koinos.InsufficientMana -> {
|
||||
add(
|
||||
NotificationUM.Koinos.InsufficientRecoverableMana(
|
||||
mana = error.manaBalance ?: BigDecimal.ZERO,
|
||||
maxMana = error.maxMana ?: BigDecimal.ZERO,
|
||||
),
|
||||
)
|
||||
}
|
||||
is BlockchainSdkError.Koinos.ManaFeeExceedsBalance -> {
|
||||
add(
|
||||
NotificationUM.Koinos.ManaExceedsBalance(
|
||||
availableKoinForTransfer = error.availableKoinForTransfer,
|
||||
onReduceClick = {
|
||||
onReduceClick(
|
||||
error.availableKoinForTransfer,
|
||||
NotificationUM.Koinos.InsufficientRecoverableMana::class.java,
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDustLimits(
|
||||
feeAmount: BigDecimal,
|
||||
receivedAmount: BigDecimal,
|
||||
dustValue: BigDecimal,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
feeCurrencyStatus: CryptoCurrencyStatus?,
|
||||
): Boolean {
|
||||
val change = when (cryptoCurrencyStatus.currency) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val balance = cryptoCurrencyStatus.value.amount ?: BigDecimal.ZERO
|
||||
balance - (feeAmount + receivedAmount)
|
||||
}
|
||||
is CryptoCurrency.Token -> {
|
||||
val balance = feeCurrencyStatus?.value?.amount ?: BigDecimal.ZERO
|
||||
balance - feeAmount
|
||||
}
|
||||
}
|
||||
|
||||
val isChangeLowerThanDust = change < dustValue && change > BigDecimal.ZERO
|
||||
return receivedAmount < dustValue || isChangeLowerThanDust
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.common.ui.tokens
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
|
||||
fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
||||
return when (val unavailabilityReason = this) {
|
||||
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_staking_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.PendingTransaction -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_pending_transaction_send,
|
||||
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||
)
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_pending_transaction_sell,
|
||||
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||
)
|
||||
}
|
||||
}
|
||||
is ScenarioUnavailabilityReason.EmptyBalance -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_empty_balance_send,
|
||||
)
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_empty_balance_sell,
|
||||
)
|
||||
}
|
||||
}
|
||||
is ScenarioUnavailabilityReason.BuyUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_buy_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.NotExchangeable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_not_exchangeable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.NotSupportedBySellService -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_sell_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.Unreachable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_generic_description,
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||
)
|
||||
ScenarioUnavailabilityReason.None -> {
|
||||
throw IllegalArgumentException("The unavailability reason must be other than None")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
package com.tangem.common.ui.tokens
|
||||
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Token item state converter from [CryptoCurrencyStatus] to [TokenItemState]
|
||||
*
|
||||
* @property appCurrency app currency
|
||||
* @property titleStateProvider title state provider
|
||||
* @property subtitleStateProvider subtitle state provider
|
||||
* @property onItemClick callback is invoked when item is clicked
|
||||
* @property onItemLongClick callback is invoked when item is long clicked
|
||||
*/
|
||||
class TokenItemStateConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = Companion::createTitleState,
|
||||
private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = {
|
||||
createSubtitleState(it, appCurrency)
|
||||
},
|
||||
private val onItemClick: (CryptoCurrencyStatus) -> Unit,
|
||||
private val onItemLongClick: ((CryptoCurrencyStatus) -> Unit)? = null,
|
||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||
|
||||
private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter)
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
||||
return when (value.value) {
|
||||
is CryptoCurrencyStatus.Loading -> value.mapToLoadingState()
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> value.mapToTokenItemState()
|
||||
is CryptoCurrencyStatus.MissedDerivation -> value.mapToNoAddressTokenItemState()
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> value.mapToUnreachableTokenItemState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToLoadingState(): TokenItemState.Loading {
|
||||
return TokenItemState.Loading(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
titleState = titleStateProvider(this) as TokenItemState.TitleState.Content,
|
||||
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToTokenItemState(): TokenItemState.Content {
|
||||
return TokenItemState.Content(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
titleState = titleStateProvider(this),
|
||||
subtitleState = requireNotNull(subtitleStateProvider(this)),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(
|
||||
text = getFormattedFiatAmount(),
|
||||
hasStaked = !getStakedBalance().isZero(),
|
||||
),
|
||||
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = getFormattedAmount()),
|
||||
onItemClick = { onItemClick(this) },
|
||||
onItemLongClick = onItemLongClick?.let {
|
||||
{ it(this) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getFormattedAmount(): String {
|
||||
val amount = value.amount?.plus(getStakedBalance()) ?: return DASH_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
|
||||
val fiatYieldBalance = value.fiatRate?.times(getStakedBalance()).orZero()
|
||||
val fiatAmount = value.fiatAmount?.plus(fiatYieldBalance) ?: return DASH_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getStakedBalance() =
|
||||
(value.yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero()
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToUnreachableTokenItemState(): TokenItemState.Unreachable {
|
||||
return TokenItemState.Unreachable(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(value = this),
|
||||
titleState = titleStateProvider(this),
|
||||
subtitleState = subtitleStateProvider(this),
|
||||
onItemClick = { onItemClick(this) },
|
||||
onItemLongClick = onItemLongClick?.let {
|
||||
{ it(this) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.mapToNoAddressTokenItemState(): TokenItemState.NoAddress {
|
||||
return TokenItemState.NoAddress(
|
||||
id = currency.id.value,
|
||||
iconState = iconStateConverter.convert(this),
|
||||
titleState = titleStateProvider(this),
|
||||
subtitleState = subtitleStateProvider(this),
|
||||
onItemLongClick = onItemLongClick?.let {
|
||||
{ it(this) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
fun createTitleState(currencyStatus: CryptoCurrencyStatus): TokenItemState.TitleState {
|
||||
return when (val value = currencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Loading,
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> {
|
||||
TokenItemState.TitleState.Content(text = currencyStatus.currency.name)
|
||||
}
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> {
|
||||
TokenItemState.TitleState.Content(
|
||||
text = currencyStatus.currency.name,
|
||||
hasPending = value.hasCurrentNetworkTransactions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createSubtitleState(
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
appCurrency: AppCurrency,
|
||||
): TokenItemState.SubtitleState? {
|
||||
return when (currencyStatus.value) {
|
||||
is CryptoCurrencyStatus.Loading -> TokenItemState.SubtitleState.Loading
|
||||
is CryptoCurrencyStatus.Loaded,
|
||||
is CryptoCurrencyStatus.Custom,
|
||||
is CryptoCurrencyStatus.NoQuote,
|
||||
is CryptoCurrencyStatus.NoAccount,
|
||||
-> currencyStatus.getCryptoPriceState(appCurrency)
|
||||
is CryptoCurrencyStatus.MissedDerivation,
|
||||
is CryptoCurrencyStatus.Unreachable,
|
||||
is CryptoCurrencyStatus.NoAmount,
|
||||
-> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getCryptoPriceState(appCurrency: AppCurrency): TokenItemState.SubtitleState {
|
||||
val fiatRate = value.fiatRate
|
||||
val priceChange = value.priceChange
|
||||
|
||||
return if (fiatRate != null && priceChange != null) {
|
||||
TokenItemState.SubtitleState.CryptoPriceContent(
|
||||
price = fiatRate.getFormattedCryptoPrice(appCurrency),
|
||||
priceChangePercent = BigDecimalFormatter.formatPercent(
|
||||
percent = priceChange,
|
||||
useAbsoluteValue = true,
|
||||
),
|
||||
type = priceChange.getPriceChangeType(),
|
||||
)
|
||||
} else {
|
||||
TokenItemState.SubtitleState.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal.getFormattedCryptoPrice(appCurrency: AppCurrency): String {
|
||||
return BigDecimalFormatter.formatFiatAmountUncapped(
|
||||
fiatAmount = this,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
}
|
||||
|
||||
private fun BigDecimal.getPriceChangeType(): PriceChangeType {
|
||||
return PriceChangeConverter.fromBigDecimal(value = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.common.ui.tokens
|
||||
|
||||
import androidx.compose.animation.Animatable
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Text view for token price.
|
||||
*
|
||||
* @param price Price of the token.
|
||||
* @param priceChangeType Type of the price change.
|
||||
*/
|
||||
@Composable
|
||||
fun TokenPriceText(price: String, modifier: Modifier = Modifier, priceChangeType: PriceChangeType? = null) {
|
||||
val growColor = TangemTheme.colors.text.accent
|
||||
val fallColor = TangemTheme.colors.text.warning
|
||||
val generalColor = TangemTheme.colors.text.primary1
|
||||
|
||||
val color = remember(generalColor) { Animatable(generalColor) }
|
||||
var animationSkipped by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(price) {
|
||||
if (animationSkipped.not()) {
|
||||
animationSkipped = true
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
if (priceChangeType != null) {
|
||||
val nextColor = when (priceChangeType) {
|
||||
PriceChangeType.UP,
|
||||
-> growColor
|
||||
PriceChangeType.DOWN -> fallColor
|
||||
PriceChangeType.NEUTRAL -> return@LaunchedEffect
|
||||
}
|
||||
|
||||
color.animateTo(nextColor, snap())
|
||||
color.animateTo(generalColor, tween(durationMillis = 500))
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = price,
|
||||
color = color.value,
|
||||
maxLines = 1,
|
||||
style = TangemTheme.typography.body2,
|
||||
overflow = TextOverflow.Visible,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
package com.tangem.common.ui.userwallet
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.coil.RotationTransformation
|
||||
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun UserWalletItem(
|
||||
state: UserWalletItemUM,
|
||||
modifier: Modifier = Modifier,
|
||||
blockColors: CardColors = TangemBlockCardColors,
|
||||
) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
colors = blockColors,
|
||||
onClick = state.onClick,
|
||||
enabled = state.isEnabled,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size68)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
CardImage(imageUrl = state.imageUrl)
|
||||
NameAndInfo(
|
||||
modifier = Modifier.weight(1f),
|
||||
name = state.name,
|
||||
information = state.information,
|
||||
)
|
||||
|
||||
when (state.endIcon) {
|
||||
UserWalletItemUM.EndIcon.None -> {}
|
||||
UserWalletItemUM.EndIcon.Arrow -> {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_chevron_right_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
UserWalletItemUM.EndIcon.Checkmark -> {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_check_24),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NameAndInfo(name: TextReference, information: TextReference, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.heightIn(min = TangemTheme.dimens.size40),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Text(
|
||||
text = name.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = information.resolveReference(),
|
||||
label = "User wallet information",
|
||||
) { information ->
|
||||
Text(
|
||||
text = information,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CardImage(imageUrl: String, modifier: Modifier = Modifier) {
|
||||
val imageModifier = modifier
|
||||
.width(TangemTheme.dimens.size24)
|
||||
.height(TangemTheme.dimens.size36)
|
||||
.clip(TangemTheme.shapes.roundedCornersSmall)
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = imageModifier,
|
||||
model = ImageRequest.Builder(LocalContext.current)
|
||||
.transformations(RotationTransformation(angle = 90f))
|
||||
.size(
|
||||
width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() },
|
||||
height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() },
|
||||
)
|
||||
.data(imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(enable = false)
|
||||
.build(),
|
||||
loading = {
|
||||
RectangleShimmer(
|
||||
modifier = imageModifier,
|
||||
radius = TangemTheme.dimens.size2,
|
||||
)
|
||||
},
|
||||
error = {
|
||||
Image(
|
||||
modifier = imageModifier,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
TangemThemePreview {
|
||||
val list = persistentListOf(
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_1".encodeToByteArray()),
|
||||
name = stringReference("My Wallet"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
),
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_2".encodeToByteArray()),
|
||||
name = stringReference("Old wallet"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageUrl = "",
|
||||
isEnabled = true,
|
||||
onClick = {},
|
||||
endIcon = UserWalletItemUM.EndIcon.Arrow,
|
||||
),
|
||||
UserWalletItemUM(
|
||||
id = UserWalletId("user_wallet_3".encodeToByteArray()),
|
||||
name = stringReference("Multi Card"),
|
||||
information = getInformation(3, "4 496,75 $"),
|
||||
imageUrl = "",
|
||||
isEnabled = false,
|
||||
endIcon = UserWalletItemUM.EndIcon.Checkmark,
|
||||
onClick = {},
|
||||
),
|
||||
)
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.background(TangemTheme.colors.background.tertiary)
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
list.fastForEach { userWalletItemUM ->
|
||||
UserWalletItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = userWalletItemUM,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInformation(cardCount: Int, totalBalance: String): TextReference {
|
||||
val t1 = TextReference.PluralRes(
|
||||
id = R.plurals.card_label_card_count,
|
||||
count = cardCount,
|
||||
formatArgs = wrappedList(cardCount),
|
||||
)
|
||||
val divider = stringReference(value = " • ")
|
||||
val t2 = stringReference(totalBalance)
|
||||
|
||||
return TextReference.Combined(wrappedList(t1, divider, t2))
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package com.tangem.common.ui.userwallet.converter
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.util.getCardsCount
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.StringsSigns.DOT
|
||||
import com.tangem.utils.StringsSigns.STARS
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
/**
|
||||
* Converter from [UserWallet] to [UserWalletItemUM]
|
||||
*
|
||||
* @property onClick lambda be invoked when item is clicked
|
||||
* @property appCurrency selected app currency
|
||||
* @property balance wallet balance
|
||||
* @property isLoading wallet loading state
|
||||
* @property isBalanceHidden wallet balance is hidden
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class UserWalletItemUMConverter(
|
||||
private val onClick: (UserWalletId) -> Unit,
|
||||
private val appCurrency: AppCurrency? = null,
|
||||
private val balance: TotalFiatBalance? = null,
|
||||
private val isLoading: Boolean = true,
|
||||
private val isBalanceHidden: Boolean = false,
|
||||
private val endIcon: UserWalletItemUM.EndIcon = UserWalletItemUM.EndIcon.None,
|
||||
) : Converter<UserWallet, UserWalletItemUM> {
|
||||
|
||||
override fun convert(value: UserWallet): UserWalletItemUM {
|
||||
return with(value) {
|
||||
UserWalletItemUM(
|
||||
id = walletId,
|
||||
name = stringReference(name),
|
||||
information = getInfo(
|
||||
appCurrency = appCurrency,
|
||||
balance = balance,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
isLoading = isLoading,
|
||||
),
|
||||
imageUrl = artworkUrl,
|
||||
isEnabled = !isLocked,
|
||||
endIcon = endIcon,
|
||||
onClick = { onClick(value.walletId) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserWallet.getInfo(
|
||||
appCurrency: AppCurrency?,
|
||||
balance: TotalFiatBalance?,
|
||||
isBalanceHidden: Boolean,
|
||||
isLoading: Boolean,
|
||||
): TextReference {
|
||||
val dividerRef = stringReference(value = " $DOT ")
|
||||
|
||||
val cardCount = getCardsCount() ?: 1
|
||||
val cardCountRef = TextReference.PluralRes(
|
||||
id = R.plurals.card_label_card_count,
|
||||
count = cardCount,
|
||||
formatArgs = wrappedList(cardCount),
|
||||
)
|
||||
|
||||
return when {
|
||||
isBalanceHidden -> combinedReference(cardCountRef, dividerRef, stringReference(STARS))
|
||||
isLocked -> combinedReference(cardCountRef, dividerRef, resourceReference(R.string.common_locked))
|
||||
isLoading -> cardCountRef
|
||||
else -> getBalanceInfo(balance, appCurrency, cardCountRef, dividerRef)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBalanceInfo(
|
||||
balance: TotalFiatBalance?,
|
||||
appCurrency: AppCurrency?,
|
||||
cardCountRef: TextReference,
|
||||
dividerRef: TextReference,
|
||||
): TextReference {
|
||||
val amount = when (balance) {
|
||||
is TotalFiatBalance.Loaded -> balance.amount
|
||||
is TotalFiatBalance.Failed,
|
||||
is TotalFiatBalance.Loading,
|
||||
null,
|
||||
-> null
|
||||
}
|
||||
|
||||
return if (amount != null && appCurrency != null) {
|
||||
val formattedAmount = BigDecimalFormatter.formatFiatAmount(
|
||||
fiatAmount = amount,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
val amountRef = stringReference(formattedAmount)
|
||||
combinedReference(cardCountRef, dividerRef, amountRef)
|
||||
} else {
|
||||
combinedReference(cardCountRef, dividerRef, stringReference(BigDecimalFormatter.EMPTY_BALANCE_SIGN))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.common.ui.userwallet.state
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import javax.annotation.concurrent.Immutable
|
||||
|
||||
@Immutable
|
||||
data class UserWalletItemUM(
|
||||
val id: UserWalletId,
|
||||
val name: TextReference,
|
||||
val information: TextReference,
|
||||
val imageUrl: String,
|
||||
val isEnabled: Boolean,
|
||||
val endIcon: EndIcon = EndIcon.None,
|
||||
val onClick: () -> Unit,
|
||||
) {
|
||||
enum class EndIcon {
|
||||
None,
|
||||
Arrow,
|
||||
Checkmark,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.core.analytics
|
||||
|
||||
interface AppInstanceIdProvider {
|
||||
|
||||
suspend fun getAppInstanceId(): String?
|
||||
|
||||
fun getAppInstanceIdSync(): String?
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.core.analytics
|
||||
|
||||
class DummyAppInstanceIdProvider : AppInstanceIdProvider {
|
||||
|
||||
override suspend fun getAppInstanceId(): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getAppInstanceIdSync(): String? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +67,16 @@ data class TokenMarketInfoResponse(
|
|||
val buyPressureChange: Change?,
|
||||
@Json(name = "experienced_buyer_change")
|
||||
val experiencedBuyerChange: Change?,
|
||||
)
|
||||
@Json(name = "networks")
|
||||
val sourceNetworks: List<SourceNetwork>?,
|
||||
) {
|
||||
data class SourceNetwork(
|
||||
@Json(name = "network_id")
|
||||
val id: String,
|
||||
@Json(name = "network_name")
|
||||
val name: String,
|
||||
)
|
||||
}
|
||||
|
||||
data class Change(
|
||||
@Json(name = "24h")
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ interface StakeKitApi {
|
|||
|
||||
@GET("yields/enabled")
|
||||
suspend fun getMultipleYields(
|
||||
@Query("preferredValidatorsOnly") preferredValidatorsOnly: Boolean? = null,
|
||||
@Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean? = null,
|
||||
@Query("type") type: YieldType? = null,
|
||||
@Query("revenueOption") revenueOption: RevenueOption? = null,
|
||||
|
|
@ -34,7 +35,7 @@ interface StakeKitApi {
|
|||
@POST("yields/balances")
|
||||
suspend fun getMultipleYieldBalances(
|
||||
@Body body: List<YieldBalanceRequestBody>,
|
||||
): ApiResponse<List<YieldBalanceWrapperDTO>>
|
||||
): ApiResponse<Set<YieldBalanceWrapperDTO>>
|
||||
|
||||
@POST("yields/{integrationId}/balances")
|
||||
suspend fun getSingleYieldBalance(
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ data class ActionRequestBodyArgs(
|
|||
@Json(name = "ledgerWalletAPICompatible")
|
||||
val ledgerWalletAPICompatible: Boolean? = null,
|
||||
@Json(name = "tronResource")
|
||||
val tronResource: String? = null,
|
||||
val tronResource: TronResource? = null,
|
||||
@Json(name = "signatureVerification")
|
||||
val signatureVerification: SignatureVerification? = null,
|
||||
@Json(name = "inputToken")
|
||||
|
|
@ -60,4 +60,12 @@ data class SignatureVerification(
|
|||
val message: String,
|
||||
@Json(name = "signed")
|
||||
val signed: String,
|
||||
)
|
||||
)
|
||||
|
||||
enum class TronResource {
|
||||
@Json(name = "ENERGY")
|
||||
ENERGY,
|
||||
|
||||
@Json(name = "BANDWIDTH")
|
||||
BANDWIDTH,
|
||||
}
|
||||
|
|
@ -2,12 +2,15 @@ package com.tangem.datasource.api.stakekit.models.response.model
|
|||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.tangem.datasource.api.stakekit.models.request.Address
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class YieldBalanceWrapperDTO(
|
||||
@Json(name = "addresses")
|
||||
val addresses: Address,
|
||||
@Json(name = "balances")
|
||||
val balances: List<BalanceDTO>,
|
||||
@Json(name = "integrationId")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.stakekit.models.response.model.transaction.tron
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TronStakeKitTransaction(
|
||||
@Json(name = "raw_data_hex")
|
||||
val rawDataHex: String,
|
||||
)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.datasource.api.tangemTech.models
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.tangem.common.extensions.calculateHashCode
|
||||
|
||||
data class UserTokensResponse(
|
||||
@Json(name = "version") val version: Int = 0,
|
||||
|
|
@ -17,7 +18,23 @@ data class UserTokensResponse(
|
|||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "decimals") val decimals: Int,
|
||||
@Json(name = "contractAddress") val contractAddress: String?,
|
||||
)
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
val otherToken = other as? Token ?: return false
|
||||
|
||||
return otherToken.contractAddress == this.contractAddress &&
|
||||
otherToken.networkId == this.networkId &&
|
||||
otherToken.derivationPath == this.derivationPath &&
|
||||
otherToken.decimals == this.decimals
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = calculateHashCode(
|
||||
contractAddress.hashCode(),
|
||||
networkId.hashCode(),
|
||||
derivationPath.hashCode(),
|
||||
decimals.hashCode(),
|
||||
)
|
||||
}
|
||||
|
||||
enum class GroupType {
|
||||
@Json(name = "none")
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
package com.tangem.datasource.di
|
||||
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.AppPreferencesUserTokensStore
|
||||
import com.tangem.datasource.local.token.UserTokensStore
|
||||
import com.tangem.datasource.local.token.UserTokensStoreMigrationRunner
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object UserTokensStoreModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserTokensStore(
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
userTokensStoreMigrationRunner: UserTokensStoreMigrationRunner,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserTokensStore {
|
||||
return AppPreferencesUserTokensStore(
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
userTokensStoreMigrationRunner = userTokensStoreMigrationRunner,
|
||||
userWalletsStore = userWalletsStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObject
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* Implementation of [UserTokensStore] that based on [appPreferencesStore]
|
||||
*
|
||||
* @property appPreferencesStore application preference store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AppPreferencesUserTokensStore(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val userTokensStoreMigrationRunner: UserTokensStoreMigrationRunner,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : UserTokensStore {
|
||||
|
||||
init {
|
||||
runUserTokensMigrations()
|
||||
}
|
||||
|
||||
override fun get(key: UserWalletId): Flow<UserTokensResponse> {
|
||||
return appPreferencesStore
|
||||
.getObject<UserTokensResponse>(PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue))
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(key: UserWalletId): UserTokensResponse? {
|
||||
return appPreferencesStore.getObjectSyncOrNull(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun store(key: UserWalletId, value: UserTokensResponse) {
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = key.stringValue),
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: delete in 5.15 (Mobile Sprint 161) [REDACTED_JIRA]
|
||||
private fun runUserTokensMigrations() {
|
||||
userWalletsStore.userWallets
|
||||
.filter { it.isNotEmpty() }
|
||||
.take(1)
|
||||
.onEach { userWallets ->
|
||||
userTokensStoreMigrationRunner.run(ids = userWallets.map { it.walletId.stringValue })
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(CoroutineScope(dispatchers.io))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +1,53 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
internal class DefaultStakingBalanceStore(
|
||||
private val dataStore: StringKeyDataStore<List<YieldBalanceWrapperDTO>>,
|
||||
private val dataStore: StringKeyDataStore<Set<YieldBalanceWrapperDTO>>,
|
||||
) : StakingBalanceStore {
|
||||
|
||||
override fun get(): Flow<List<YieldBalanceWrapperDTO>> {
|
||||
return dataStore.get(STAKING_BALANCE_KEY)
|
||||
private val mutex = Mutex()
|
||||
|
||||
override fun get(userWalletId: UserWalletId): Flow<Set<YieldBalanceWrapperDTO>> {
|
||||
return dataStore.get(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(): List<YieldBalanceWrapperDTO>? {
|
||||
return dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalanceWrapperDTO>? {
|
||||
return dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
}
|
||||
|
||||
override suspend fun store(items: List<YieldBalanceWrapperDTO>) {
|
||||
return dataStore.store(STAKING_BALANCE_KEY, items)
|
||||
override suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
|
||||
mutex.withLock {
|
||||
dataStore.store(userWalletId.stringValue, items)
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(integrationId: String): Flow<List<BalanceDTO>> {
|
||||
return dataStore.get(STAKING_BALANCE_KEY)
|
||||
.map { balances ->
|
||||
balances.filter { it.integrationId == integrationId }
|
||||
.flatMap { it.balances }
|
||||
override fun get(userWalletId: UserWalletId, integrationId: String): Flow<YieldBalanceWrapperDTO> {
|
||||
return dataStore.get(userWalletId.stringValue)
|
||||
.mapNotNull { balances ->
|
||||
balances.firstOrNull { it.integrationId == integrationId }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSyncOrNull(integrationId: String): List<BalanceDTO>? {
|
||||
return dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
?.firstOrNull { it.integrationId == integrationId }?.balances
|
||||
override suspend fun getSyncOrNull(userWalletId: UserWalletId, integrationId: String): YieldBalanceWrapperDTO? {
|
||||
return dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
?.firstOrNull { it.integrationId == integrationId }
|
||||
}
|
||||
|
||||
override suspend fun store(integrationId: String, item: YieldBalanceWrapperDTO) {
|
||||
val balances = dataStore.getSyncOrNull(STAKING_BALANCE_KEY)
|
||||
?.toMutableList()
|
||||
?.addOrReplace(item) { item.integrationId == integrationId }
|
||||
?: listOf(item)
|
||||
override suspend fun store(userWalletId: UserWalletId, integrationId: String, item: YieldBalanceWrapperDTO) {
|
||||
mutex.withLock {
|
||||
val balances = dataStore.getSyncOrNull(userWalletId.stringValue)
|
||||
?.addOrReplace(item) { it.integrationId == integrationId }
|
||||
?: setOf(item)
|
||||
|
||||
return dataStore.store(STAKING_BALANCE_KEY, balances)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val STAKING_BALANCE_KEY = "STAKING_BALANCE_KEY"
|
||||
dataStore.store(userWalletId.stringValue, balances)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface StakingBalanceStore {
|
||||
|
||||
fun get(): Flow<List<YieldBalanceWrapperDTO>>
|
||||
fun get(userWalletId: UserWalletId): Flow<Set<YieldBalanceWrapperDTO>>
|
||||
|
||||
suspend fun getSyncOrNull(): List<YieldBalanceWrapperDTO>?
|
||||
suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalanceWrapperDTO>?
|
||||
|
||||
suspend fun store(items: List<YieldBalanceWrapperDTO>)
|
||||
suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>)
|
||||
|
||||
fun get(integrationId: String): Flow<List<BalanceDTO>>
|
||||
fun get(userWalletId: UserWalletId, integrationId: String): Flow<YieldBalanceWrapperDTO>
|
||||
|
||||
suspend fun getSyncOrNull(integrationId: String): List<BalanceDTO>?
|
||||
suspend fun getSyncOrNull(userWalletId: UserWalletId, integrationId: String): YieldBalanceWrapperDTO?
|
||||
|
||||
suspend fun store(integrationId: String, item: YieldBalanceWrapperDTO)
|
||||
suspend fun store(userWalletId: UserWalletId, integrationId: String, item: YieldBalanceWrapperDTO)
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Deprecated(
|
||||
message = "Use AppPreferencesStore",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "AppPreferencesStore",
|
||||
imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"),
|
||||
),
|
||||
level = DeprecationLevel.WARNING,
|
||||
)
|
||||
interface UserTokensStore {
|
||||
|
||||
@Deprecated(
|
||||
message = "Use getObject",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "appPreferencesStore.getObject(userWalletId)",
|
||||
imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"),
|
||||
),
|
||||
level = DeprecationLevel.WARNING,
|
||||
)
|
||||
fun get(key: UserWalletId): Flow<UserTokensResponse>
|
||||
|
||||
@Deprecated(
|
||||
message = "Use getObjectSyncOrNull",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "appPreferencesStore.getObjectSyncOrNull(userWalletId)",
|
||||
imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"),
|
||||
),
|
||||
level = DeprecationLevel.WARNING,
|
||||
)
|
||||
suspend fun getSyncOrNull(key: UserWalletId): UserTokensResponse?
|
||||
|
||||
@Deprecated(
|
||||
message = "Use storeObject",
|
||||
replaceWith = ReplaceWith(
|
||||
expression = "appPreferencesStore.storeObject(userWalletId, response)",
|
||||
imports = arrayOf("com.tangem.datasource.local.preferences.AppPreferencesStore"),
|
||||
),
|
||||
level = DeprecationLevel.WARNING,
|
||||
)
|
||||
suspend fun store(key: UserWalletId, value: UserTokensResponse)
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import androidx.datastore.core.DataMigration
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
|
||||
/**
|
||||
* Migration of saving [UserTokensResponse] from file to [AppPreferencesStore]
|
||||
*
|
||||
* @param userWalletId user wallet id
|
||||
* @param moshi moshi
|
||||
* @property fileReader file reader
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class UserTokensStoreMigration(
|
||||
userWalletId: String,
|
||||
moshi: Moshi,
|
||||
private val fileReader: FileReader,
|
||||
) : DataMigration<AppPreferencesStore> {
|
||||
|
||||
private val legacyFileName = "user_tokens_$userWalletId"
|
||||
private val keyName = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId)
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private val adapter = moshi.adapter<UserTokensResponse>()
|
||||
|
||||
override suspend fun shouldMigrate(currentData: AppPreferencesStore): Boolean = true
|
||||
|
||||
override suspend fun migrate(currentData: AppPreferencesStore): AppPreferencesStore {
|
||||
val currentKey = currentData.getObjectSyncOrNull<UserTokensResponse>(key = keyName)
|
||||
|
||||
if (currentKey != null) return currentData
|
||||
|
||||
val value = runCatching {
|
||||
val json = fileReader.readFile(legacyFileName)
|
||||
adapter.fromJson(json)
|
||||
}.getOrNull()
|
||||
|
||||
if (value != null) {
|
||||
currentData.storeObject(key = keyName, value = value)
|
||||
}
|
||||
|
||||
return currentData
|
||||
}
|
||||
|
||||
override suspend fun cleanUp() {
|
||||
fileReader.removeFile(legacyFileName)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package com.tangem.datasource.local.token
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.files.FileReader
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Runner that launch migrations of saving user tokens store
|
||||
*
|
||||
* @property appPreferencesStore application preference store
|
||||
* @property fileReader file reader
|
||||
* @property moshi moshi
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
class UserTokensStoreMigrationRunner @Inject constructor(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val fileReader: FileReader,
|
||||
@NetworkMoshi private val moshi: Moshi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend fun run(ids: List<String>) {
|
||||
ids.forEach { id ->
|
||||
coroutineScope { run(id) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun run(id: String) {
|
||||
withContext(dispatchers.io) {
|
||||
val migration = UserTokensStoreMigration(
|
||||
userWalletId = id,
|
||||
moshi = moshi,
|
||||
fileReader = fileReader,
|
||||
)
|
||||
|
||||
migration.migrate(appPreferencesStore)
|
||||
|
||||
migration.cleanUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ class DefaultAppComponentContext(
|
|||
messageHandler: UiMessageHandler,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
override val hiltComponentBuilder: DecomposeComponent.Builder,
|
||||
private val replaceRouter: Router? = null,
|
||||
) : AppComponentContext, ComponentContext by componentContext {
|
||||
|
||||
override val tags: HashMap<String, Any> = HashMap()
|
||||
|
|
@ -31,5 +32,5 @@ class DefaultAppComponentContext(
|
|||
get() = instanceKeeper.getOrCreate { DefaultAppNavigationProvider() }
|
||||
|
||||
override val router: Router
|
||||
get() = instanceKeeper.getOrCreate { DefaultRouter(navigationProvider) }
|
||||
get() = replaceRouter ?: instanceKeeper.getOrCreate { DefaultRouter(navigationProvider) }
|
||||
}
|
||||
|
|
@ -46,14 +46,36 @@ abstract class Model : InstanceKeeper.Instance {
|
|||
progressFlow: MutableSharedFlow<Boolean>,
|
||||
dispatcher: CoroutineDispatcher = dispatchers.mainImmediate,
|
||||
crossinline block: suspend () -> Unit,
|
||||
): Job = resource(
|
||||
acquire = { progressFlow.emit(true) },
|
||||
release = { progressFlow.emit(false) },
|
||||
dispatcher = dispatcher,
|
||||
block = block,
|
||||
)
|
||||
|
||||
/**
|
||||
* Launches [block] in the model's scope and acquires a resource before executing the block and releases it after.
|
||||
*
|
||||
* @param acquire The block of code to acquire the resource.
|
||||
* @param release The block of code to release the resource.
|
||||
* @param dispatcher The [CoroutineDispatcher] to launch the coroutine. Default is [Dispatchers.Main.immediate].
|
||||
* @param block The block of code to execute.
|
||||
*
|
||||
* @return The [Job] of the launched coroutine.
|
||||
* */
|
||||
protected inline fun resource(
|
||||
crossinline acquire: suspend () -> Unit,
|
||||
crossinline release: suspend () -> Unit,
|
||||
dispatcher: CoroutineDispatcher = dispatchers.mainImmediate,
|
||||
crossinline block: suspend () -> Unit,
|
||||
): Job = modelScope.launch(dispatcher) {
|
||||
progressFlow.emit(value = true)
|
||||
acquire()
|
||||
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
withContext(NonCancellable) {
|
||||
progressFlow.emit(value = false)
|
||||
release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,10 +100,10 @@
|
|||
<string name="common_enable">Aktivieren</string>
|
||||
<string name="common_enabled">Aktiviert</string>
|
||||
<string name="common_error">Fehler</string>
|
||||
<string name="common_exchange">Umtausch</string>
|
||||
<string name="common_explore">Erkunden</string>
|
||||
<string name="common_explore_transaction_history">Transaktionsverlauf einsehen</string>
|
||||
<string name="common_explorer">Explorer</string>
|
||||
<string name="common_fee_label">Gebühr</string>
|
||||
<string name="common_fee_selector_footer">Netzwerkgebühren sind Gebühren, die Nutzer für die Verarbeitung und Bestätigung von Transaktionen zahlen. Die Höhe der Gebühren kann von der Überlastung des Netzes, der Größe der Transaktion und der Ausführungspriorität abhängen. %s</string>
|
||||
<string name="common_fee_selector_option_fast">Schnell</string>
|
||||
<string name="common_fee_selector_option_market">Markt</string>
|
||||
|
|
@ -113,6 +113,10 @@
|
|||
<string name="common_go_to_provider">Zum Anbieter gehen</string>
|
||||
<string name="common_go_to_token">Zum Token</string>
|
||||
<string name="common_import">Importieren</string>
|
||||
<plurals name="common_in_days">
|
||||
<item quantity="one">in %d Tag</item>
|
||||
<item quantity="other">in %d Tagen</item>
|
||||
</plurals>
|
||||
<string name="common_later">Später</string>
|
||||
<string name="common_locked">Gesperrt</string>
|
||||
<string name="common_main_network">Hauptnetz</string>
|
||||
|
|
@ -168,7 +172,9 @@
|
|||
<string name="custom_token_contract_address_input_title">Vertragsadresse</string>
|
||||
<string name="custom_token_creation_error_invalid_contract_address">Vertragsadresse ist ungültig</string>
|
||||
<string name="custom_token_creation_error_network_not_selected">Bitte wähle das Netzwerk</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Dezimalzahl muss eine gültige Ganzzahl sein, bis zu %li</string>
|
||||
<string name="custom_token_creation_error_token_already_exist_message">Dieses Token wurde bereits zur Liste hinzugefügt</string>
|
||||
<string name="custom_token_creation_error_token_already_exist_title">Token existiert bereits</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Dezimalzahl muss eine gültige Ganzzahl sein, bis zu %d</string>
|
||||
<string name="custom_token_custom_derivation">Benutzerdefinierte Ableitung(derivation)</string>
|
||||
<string name="custom_token_custom_derivation_placeholder">E. g. m/00\'/0000\'/0\'/0/0</string>
|
||||
<string name="custom_token_custom_derivation_title">Benutzerdefinierte Ableitung (Derivation) eingeben</string>
|
||||
|
|
@ -338,12 +344,14 @@
|
|||
<string name="markets_add_to_my_portfolio_description">Um mit dem Kauf, Tausch oder Erhalt dieses Vermögenswerts zu beginnen, füge diesen Token zu mindestens 1 Netzwerk hinzu</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_description">Dieses Asset ist nicht verfügbar</string>
|
||||
<string name="markets_add_to_portfolio_button">Zum Portfolio hinzufügen</string>
|
||||
<string name="markets_add_token">Token hinzufügen</string>
|
||||
<string name="markets_add_token">Hinzufügen</string>
|
||||
<string name="markets_available_networks">Verfügbare Netzwerke</string>
|
||||
<string name="markets_common_my_portfolio">Mein Portfolio</string>
|
||||
<string name="markets_common_title">Markt</string>
|
||||
<string name="markets_generate_addresses_notification">Um Adressen für ausgewählte Netzwerke zu generieren, musst du eine Tangem-Karte scannen</string>
|
||||
<string name="markets_insights_info_description_message">Die Daten dieses Abschnitts stammen aus den folgenden Netzwerken: %s</string>
|
||||
<string name="markets_loading_error_title">Die Daten konnten nicht geladen werden...</string>
|
||||
<string name="markets_loading_no_data_title">Keine Daten</string>
|
||||
<string name="markets_quick_actions">Schnelle Aktionen</string>
|
||||
<string name="markets_search_result_title">Ergebnis</string>
|
||||
<string name="markets_search_see_tokens_under_100k">Token unter 100k Marktkapitalisierung anzeigen</string>
|
||||
|
|
@ -369,7 +377,7 @@
|
|||
<item quantity="one">Bewertung, basierend auf %d</item>
|
||||
<item quantity="other">Bewertungen, basierend auf %d</item>
|
||||
</plurals>
|
||||
<string name="markets_token_details_blockchain_site">Blockchain-Site</string>
|
||||
<string name="markets_token_details_blockchain_site">Webseite</string>
|
||||
<string name="markets_token_details_buy_pressure">Kaufdruck</string>
|
||||
<string name="markets_token_details_buy_pressure_description">Die Differenz zwischen Käufer- und Verkäufervolumen</string>
|
||||
<string name="markets_token_details_circulating_supply">Umlaufmenge</string>
|
||||
|
|
@ -465,7 +473,7 @@
|
|||
<string name="onboarding_seed_intro_title">Seed-Phrase verwenden</string>
|
||||
<string name="onboarding_seed_mnemonic_invalid_checksum">Ungültige Seed-Phrase. Bitte überprüfe die Wortreihenfolge.</string>
|
||||
<string name="onboarding_seed_mnemonic_wrong_words">Ungültige Seed-Phrase. Bitte überprüfe die Rechtschreibung.</string>
|
||||
<string name="onboarding_seed_phrase_intro_legacy">veralteter Standard</string>
|
||||
<string name="onboarding_seed_phrase_intro_legacy">Veralteter Standard</string>
|
||||
<string name="onboarding_seed_user_validation_message">Um zu überprüfen, ob du deine Seed-Phrase richtig aufgeschrieben hast, gib bitte das 2., 7. und 11 Wort ein.</string>
|
||||
<string name="onboarding_seed_user_validation_title">Eine letzte Prüfung!</string>
|
||||
<string name="onboarding_subtitle_no_backup_cards">Um den Sicherungsvorgang zu starten, füge bis zu zwei Sicherungskarten hinzu.</string>
|
||||
|
|
@ -571,18 +579,11 @@
|
|||
<string name="send_custom_kaspa_per_utxo_footer">Die Gebühr, die für die Nutzung jeder nicht ausgegebenen Transaktionsausgabe (UTXO) im Kaspa-Netzwerk erforderlich ist. Je mehr UTXOs du in einer Transaktion verwenden, desto höher ist die Gebühr.</string>
|
||||
<string name="send_custom_kaspa_per_utxo_title">KAS per UTXO</string>
|
||||
<string name="send_date_format">%1$s, %2$s</string>
|
||||
<string name="send_destination_hint_address">Adresse</string>
|
||||
<string name="send_destination_tag_field">Ziel-Tag</string>
|
||||
<string name="send_enter_address_field">Adresse eingeben</string>
|
||||
<string name="send_error_address_same_as_wallet">Die Adresse stimmt mit der Adresse Ihrer Brieftasche überein</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Ungültiges Tag. Es wird der Transaktion nicht hinzugefügt.</string>
|
||||
<string name="send_extras_error_invalid_memo">Ungültiges Memo. Sie wird der Transaktion nicht hinzugefügt.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">inkl. Gebühr</string>
|
||||
<string name="send_fee_picker_low">Niedrig</string>
|
||||
<string name="send_fee_picker_normal">Normal</string>
|
||||
<string name="send_fee_picker_priority">Priorität</string>
|
||||
<string name="send_fee_unreachable_error_text">Überprüfe deine Netzwerkverbindung</string>
|
||||
<string name="send_fee_unreachable_error_title">Informationen zur Netzwerkgebühr nicht erreichbar</string>
|
||||
<string name="send_from_wallet_android">Von</string>
|
||||
|
|
@ -597,7 +598,7 @@
|
|||
<string name="send_network_fee_warning_title">Abdeckung der Netzgebühren</string>
|
||||
<string name="send_notification_exceed_balance_text">Unzureichende Mittel für die Überweisung, da die Summe aus Gebühr und Überweisungsbetrag das bestehende Guthaben übersteigt</string>
|
||||
<string name="send_notification_exceed_balance_title">Gesamtbetrag übersteigt den Saldo</string>
|
||||
<string name="send_notification_existential_deposit_text">Das Konto wird von der Blockchain gelöscht, wenn der Kontostand unter die Mindesteinlage fällt. Bitte belasse %s auf deinem Konto.</string>
|
||||
<string name="send_notification_existential_deposit_text">Ein Guthaben von mindestens %s ist erforderlich, um dein Konto in der Blockchain aktiv zu halten und Sicherheitsrisiken zu vermeiden. Dieser Betrag verbleibt auf deinem Guthaben und kann nicht abgehoben werden.</string>
|
||||
<string name="send_notification_existential_deposit_title">Mindesteinlage</string>
|
||||
<string name="send_notification_fee_too_high_text">Der Kommissionsbetrag ist %s mal der empfohlene Betrag. Stelle sicher, dass die benutzerdefinierten Einstellungen korrekt sind.</string>
|
||||
<string name="send_notification_fee_too_high_title">Die individuelle Gebühr ist hoch</string>
|
||||
|
|
@ -631,21 +632,15 @@
|
|||
<string name="send_summary_title">Versende %s</string>
|
||||
<string name="send_summary_transaction_description">Du sendest **%1$s** inklusive der Netzwerkgebühr %2$s</string>
|
||||
<string name="send_summary_transaction_description_no_fiat_fee">Du sendest **%1$s** und %2$s</string>
|
||||
<string name="send_title_currency_format">Senden %s</string>
|
||||
<string name="send_total_label">Gesamt</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s und %2$s werden gesendet</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (inkl. Gebühr: %2$s)</string>
|
||||
<string name="send_total_subtitle_format">%s wird gesendet</string>
|
||||
<string name="send_transaction_success">Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert</string>
|
||||
<string name="send_tron_account_activation_error">%1$s ist ein Vermögenswert im Tron-Netzwerk. Um die Gebühr zu berechnen und eine Transaktion durchzuführen, musst du etwas Tron (TRX) auf deinem Konto einzahlen.</string>
|
||||
<string name="send_validation_invalid_address">Ungültige Adresse</string>
|
||||
<string name="sent_transaction_sent_title">Transaktion gesendet</string>
|
||||
<string name="settings_card_settings_footer">Bereite das Scannen der Karte vor, die du einrichten möchtest.</string>
|
||||
<string name="settings_forget_wallet">Entferne diese Wallet</string>
|
||||
<string name="settings_forget_wallet_footer">Hiermit wird die Wallet aus der Anwendung entfernt. Die Wallet selbst kann wieder hinzugefügt werden.</string>
|
||||
<string name="settings_wallet_name_title">Name</string>
|
||||
<string name="staking_active">Aktiv</string>
|
||||
<string name="staking_active_footer">Um deine Kryptos zu unstaken, klick hier.</string>
|
||||
<string name="staking_active_footer">Um deine Vermögenswerte freizugeben, tippe auf den Block oben</string>
|
||||
<string name="staking_amount_requirement_error">Die Anzahl der zu stakenden Krypros muss mindesten %s betragen</string>
|
||||
<string name="staking_claim_unstaked">Nicht gestakte beanspruche</string>
|
||||
<string name="staking_details_annual_percentage_rate">Jährliche prozentuale Rendite</string>
|
||||
|
|
@ -658,12 +653,12 @@
|
|||
<string name="staking_details_market_rating">Marktbewertung</string>
|
||||
<string name="staking_details_metrics_block_header">Metriken</string>
|
||||
<string name="staking_details_minimum_requirement">Mindestanforderungen</string>
|
||||
<string name="staking_details_no_rewards_to_claim">Keine Belohnungen zu beanspruchen.</string>
|
||||
<string name="staking_details_no_rewards_to_claim">Keine Belohnungen</string>
|
||||
<string name="staking_details_reward_claiming">Belohnungen beanspruchen</string>
|
||||
<string name="staking_details_reward_claiming_info">Eine Möglichkeit, Staking-Belohnungen zu erhalten. Es kann automatisch oder manuell beansprucht werden.</string>
|
||||
<string name="staking_details_reward_schedule">Belohnungszeitplan</string>
|
||||
<string name="staking_details_reward_schedule_info">Dabei handelt es sich um einen Zeitplan, der festlegt, wann die Teilnehmer am Staking ihre Belohnungen erhalten.</string>
|
||||
<string name="staking_details_rewards_to_claim">Belohnungen, die du beanspruchen kannst: %s</string>
|
||||
<string name="staking_details_rewards_to_claim">Belohnungen %s</string>
|
||||
<string name="staking_details_title">Staking %s</string>
|
||||
<string name="staking_details_unbonding_period">Entbindungsdauer</string>
|
||||
<string name="staking_details_unbonding_period_info">Der Zeitraum, den du nach der Beantragung der Abhebung von Geldern aus dem Staking warten musst, bevor die Token verfügbar werden.</string>
|
||||
|
|
@ -677,6 +672,7 @@
|
|||
<string name="staking_notification_earn_rewards_text_period_week">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Woche.</string>
|
||||
<string name="staking_notification_earn_rewards_title">Verdiene Staking-Belohnungen</string>
|
||||
<string name="staking_notification_unstake_text">Die Belohnungen werden sofort nach dem unstaken gestoppt. Der unstakingprozess dauert %s.</string>
|
||||
<string name="staking_ready_to_withdraw">Bereit zum Abheben</string>
|
||||
<string name="staking_rebond">Erneut binden</string>
|
||||
<string name="staking_restake">Erneut staken</string>
|
||||
<string name="staking_restake_rewards">Belohnungen erneut staken</string>
|
||||
|
|
@ -697,6 +693,7 @@
|
|||
<string name="staking_stake_more">Mehr staken</string>
|
||||
<string name="staking_title_stake">Stake %s</string>
|
||||
<string name="staking_title_unstake">Staking beenden%s</string>
|
||||
<string name="staking_unbonding">Lösen der Bindungen</string>
|
||||
<string name="staking_unlocked_locked">Gelocktes unlocken</string>
|
||||
<string name="staking_unstaked">Unstaken</string>
|
||||
<string name="staking_unstaked_footer">Prüfe, was nicht eingesetzt wurde, um dein Vermögen zu beanspruchen</string>
|
||||
|
|
@ -834,6 +831,8 @@
|
|||
<string name="wallet_settings_title">Wallet-Einstellungen</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">Verwende %s oder scanne eine Karte, um den Zugriff auf deine Wallet freizuschalten.</string>
|
||||
<string name="warning_approval_in_progress_message">Das Genehmigungsverfahren ist derzeit im Gange und wird in Kürze abgeschlossen sein</string>
|
||||
<string name="warning_approval_in_progress_title">Genehmigung läuft</string>
|
||||
<string name="warning_backup_errors_message">Es scheint, dass die Aktivierung der Karte nicht korrekt abgeschlossen wurde. Dies kann an einem Problem mit dem NFC-Modul deines Gerätes oder an einem falschen Tippen der Karte auf dein Gerät liegen. Bitte wende dich an unser Support-Team, um Unterstützung zu erhalten.</string>
|
||||
<string name="warning_backup_errors_title">Aktivierungsfehler</string>
|
||||
<string name="warning_beacon_chain_retirement_content">Laut den Entwicklern des BNB-Netzes wird die Unterstützung für den BEP-2-Standard im Juni 2024 enden. Um den Verlust von Vermögenswerten mit diesem Standard zu vermeiden, konvertiere bitte in den BEP-20 Standard. Nutze gerne unseren Swap-Service, um sie auf das BNB Smart Chain Netzwerk zu übertragen.</string>
|
||||
|
|
@ -875,8 +874,8 @@
|
|||
<string name="warning_low_signatures_message">Auf dieser Karte sind nur noch %s Unterschriften übrig. Du musst dein gesamtes Guthaben abheben.</string>
|
||||
<string name="warning_low_signatures_title">Geringe Anzahl von Unterschriften</string>
|
||||
<string name="warning_manage_tokens_legacy_derivation_message">Token in verschiedenen Netzwerken können unterschiedliche Adressen haben. Überprüfe bei der Überweisung noch einmal, ob deine Adresse mit der des Netzwerks übereinstimmt.</string>
|
||||
<string name="warning_matic_migration_title">Migration von MATIC zu POL</string>
|
||||
<string name="warning_matic_migration_message">MATIC wird auf POL migriert. Es gibt jedoch keine Frist, und MATIC wird noch nicht abgeschafft. Du kannst MATIC-Token weiterhin verwenden oder sie über eien Exchange gegen POL tauschen.</string>
|
||||
<string name="warning_matic_migration_title">Migration von MATIC zu POL</string>
|
||||
<plurals name="warning_missing_derivation_message">
|
||||
<item quantity="one">Verwende deine Karte, um eine Adresse für das %d-Netz zu erhalten</item>
|
||||
<item quantity="other">Verwende deine Karte, um mehrere Adressen für die %d-Netzwerke zu erhalten</item>
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@
|
|||
<string name="common_explore">Explorez</string>
|
||||
<string name="common_explore_transaction_history">Explorez l\'historique des transactions</string>
|
||||
<string name="common_explorer">Explorateur</string>
|
||||
<string name="common_fee_label">Commissions</string>
|
||||
<string name="common_fee_selector_footer">Les frais de réseau sont des charges que les utilisateurs paient pour traiter et confirmer les transactions. Le montant des frais peut être affecté par la congestion du réseau, la taille de la transaction et la priorité d\'exécution. %s</string>
|
||||
<string name="common_fee_selector_option_fast">Rapide</string>
|
||||
<string name="common_fee_selector_option_market">Marché</string>
|
||||
|
|
@ -164,7 +163,7 @@
|
|||
<string name="custom_token_contract_address_input_title">Adresse du contrat</string>
|
||||
<string name="custom_token_creation_error_invalid_contract_address">L\'adresse du contrat n\'est pas valide</string>
|
||||
<string name="custom_token_creation_error_network_not_selected">Veuillez sélectionner le réseau</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Les décimales doivent être un entier valide, jusqu\'à %li</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Les décimales doivent être un entier valide, jusqu\'à %d</string>
|
||||
<string name="custom_token_custom_derivation">Dérivation personnalisée</string>
|
||||
<string name="custom_token_custom_derivation_placeholder">Par exemple m/00\'/0000\'/0\'/0/0</string>
|
||||
<string name="custom_token_custom_derivation_title">Entrez une dérivation personnalisée</string>
|
||||
|
|
@ -508,18 +507,11 @@
|
|||
<string name="send_custom_kaspa_per_utxo_footer">Les frais requis pour l\'utilisation de chaque sortie de transaction non dépensée (UTXO) dans le réseau Kaspa. Plus vous utilisez d’UTXO dans une transaction, plus les frais seront élevés.</string>
|
||||
<string name="send_custom_kaspa_per_utxo_title">KAS pour UTXO</string>
|
||||
<string name="send_date_format">%1$s, %2$s</string>
|
||||
<string name="send_destination_hint_address">Adresse</string>
|
||||
<string name="send_destination_tag_field">ID de destination</string>
|
||||
<string name="send_enter_address_field">Entrez l\'adresse</string>
|
||||
<string name="send_error_address_same_as_wallet">L\'adresse est la même que celle de votre portefeuille</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Tag invalide. Il ne sera pas ajouté à la transaction.</string>
|
||||
<string name="send_extras_error_invalid_memo">Mémo invalide. Il ne sera pas ajouté à la transaction.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">Inclure les commissions</string>
|
||||
<string name="send_fee_picker_low">Bas</string>
|
||||
<string name="send_fee_picker_normal">Normal</string>
|
||||
<string name="send_fee_picker_priority">Priorité</string>
|
||||
<string name="send_fee_unreachable_error_text">Vérifiez votre connexion réseau</string>
|
||||
<string name="send_fee_unreachable_error_title">Informations sur les frais de réseau inaccessibles</string>
|
||||
<string name="send_from_wallet_android">De</string>
|
||||
|
|
@ -534,7 +526,7 @@
|
|||
<string name="send_network_fee_warning_title">Couverture des frais de réseau</string>
|
||||
<string name="send_notification_exceed_balance_text">Fonds insuffisants pour le transfert, car le total des frais et du montant du transfert dépasse le solde existant</string>
|
||||
<string name="send_notification_exceed_balance_title">Le total dépasse le solde</string>
|
||||
<string name="send_notification_existential_deposit_text">Le compte sera effacé de la blockchain si un solde descend en dessous du dépôt existentiel. Veuillez en laisser %s sur votre solde.</string>
|
||||
<string name="send_notification_existential_deposit_text">Un solde d\'au moins %s est requis pour conserver votre compte sur la blockchain afin d\'éviter les risques de sécurité. Ce montant restera sur votre solde et ne pourra pas être retiré.</string>
|
||||
<string name="send_notification_existential_deposit_title">Dépôt existentiel</string>
|
||||
<string name="send_notification_fee_too_high_text">Le montant de la commission est %s fois le montant recommandé. Assurez-vous que les paramètres personnalisés sont corrects.</string>
|
||||
<string name="send_notification_fee_too_high_title">Les frais de douane sont élevés</string>
|
||||
|
|
@ -568,21 +560,16 @@
|
|||
<string name="send_summary_title">Envoyer %s</string>
|
||||
<string name="send_summary_transaction_description">Vous envoyez **%1$s** incluant des frais de réseau de %2$s</string>
|
||||
<string name="send_summary_transaction_description_no_fiat_fee">Vous envoyez **%1$s** et %2$s</string>
|
||||
<string name="send_title_currency_format">Envoi de %s</string>
|
||||
<string name="send_total_label">Total</string>
|
||||
<string name="send_total_subtitle_asset_format">Sera envoyé %1$s et %2$s</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (incl. les commissions : %2$s)</string>
|
||||
<string name="send_total_subtitle_format">Sera envoyé %s</string>
|
||||
<string name="send_transaction_success">La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps</string>
|
||||
<string name="send_tron_account_activation_error">%1$s est un actif du réseau Tron. Pour calculer les frais et effectuer une transaction, déposez du Tron (TRX) sur votre compte.</string>
|
||||
<string name="send_validation_invalid_address">Adresse incorrecte</string>
|
||||
<string name="sent_transaction_sent_title">Transaction envoyée</string>
|
||||
<string name="settings_card_settings_footer">Scannez la carte que vous souhaitez configurer.</string>
|
||||
<string name="settings_forget_wallet">Oublier le portefeuille</string>
|
||||
<string name="settings_forget_wallet_footer">Cela supprimera le portefeuille de l\'application. Le portefeuille lui-même peut être ajouté à nouveau.</string>
|
||||
<string name="settings_wallet_name_title">Nom</string>
|
||||
<string name="staking_active">Actif</string>
|
||||
<string name="staking_active_footer">Afin d\'unstaker vos actifs, cliquez ici.</string>
|
||||
<string name="staking_active_footer">Afin d\'unstaker vos actifs, appuyez sur le bloc ci-dessus</string>
|
||||
<string name="staking_details_annual_percentage_rate">Pourcentage de rendement annuel</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">Le pourcentage de rendement annuel que vous pouvez gagner en participant au staking.</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
<string name="staking_details_available">Disponible</string>
|
||||
|
|
@ -604,6 +591,7 @@
|
|||
<string name="staking_details_warmup_period_info">Le temps imparti pour activer la participation au staking.</string>
|
||||
<string name="staking_native">Native staking</string>
|
||||
<string name="staking_notification_earn_rewards_title">Gagnez des récompenses de staking</string>
|
||||
<string name="staking_notification_unstake_text">Les récompenses cessent de s\'accumuler immédiatement après que vous ayez commencé le unstaking. Le processus de unstaking prend %s.</string>
|
||||
<string name="staking_rewards">Récompenses</string>
|
||||
<string name="staking_stake_locked">Stake verrouillé</string>
|
||||
<string name="staking_stake_more">Staker plus</string>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
<string name="common_delete">Rimuovere</string>
|
||||
<string name="common_done">Fatto</string>
|
||||
<string name="common_error">Errore</string>
|
||||
<string name="common_fee_label">Commissione</string>
|
||||
<string name="common_network_fee_title">Costi della rete</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_save_changes">Mantieni le modifiche</string>
|
||||
|
|
@ -35,21 +34,10 @@
|
|||
<string name="initial_message_tap_header">Avvicina la carta</string>
|
||||
<string name="onboarding_create_wallet_button_create_wallet">Crea portafoglio</string>
|
||||
<string name="send_amount_label">Importo</string>
|
||||
<string name="send_destination_hint_address">Indirizzo</string>
|
||||
<string name="send_error_address_same_as_wallet">L\'indirizzo corrisponde all\'indirizzo del tuo portafoglio</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">Includi commissione</string>
|
||||
<string name="send_fee_picker_low">Insufficiente</string>
|
||||
<string name="send_fee_picker_normal">Normale</string>
|
||||
<string name="send_fee_picker_priority">Prioritario</string>
|
||||
<string name="send_max_amount_label">Importo totale</string>
|
||||
<string name="send_total_label">Totale</string>
|
||||
<string name="send_total_subtitle_asset_format">Sarà inviato %1$s e %2$s</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (inc. commissione: %2$s)</string>
|
||||
<string name="send_total_subtitle_format">Sarà inviato %s</string>
|
||||
<string name="send_transaction_success">La transazione è stata firmata con successo e inviata al nodo blockchain. Il saldo del portafoglio verrà aggiornato dopo un po\' di tempo</string>
|
||||
<string name="send_validation_invalid_address">Indirizzo non valido</string>
|
||||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="wallet_connect_title">WalletConnect</string>
|
||||
<string name="wallet_notification_address_copied">L\'indirizzo è stato copiato con successo</string>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<string name="biometric_lockout_warning_title">試行回数が多すぎます</string>
|
||||
<string name="biometric_unavailable_warning">お使いの携帯電話で生体認証が無効になっているため、アプリにウォレットを保存できません。ウォレットを保存するには、携帯電話の設定で生体認証機能を有効にしてください。</string>
|
||||
<string name="button_start_backup_process">バックアップ処理を開始する</string>
|
||||
<string name="buy_token_description">法定通貨カードまたは銀行口座から</string>
|
||||
<string name="buy_token_description">銀行カードまたは銀行口座から</string>
|
||||
<plurals name="card_label_card_count">
|
||||
<item quantity="other">%d カード</item>
|
||||
</plurals>
|
||||
|
|
@ -98,10 +98,10 @@
|
|||
<string name="common_enable">有効にする</string>
|
||||
<string name="common_enabled">有効</string>
|
||||
<string name="common_error">エラー</string>
|
||||
<string name="common_exchange">交換</string>
|
||||
<string name="common_explore">移動する</string>
|
||||
<string name="common_explore_transaction_history">取引履歴を調べる</string>
|
||||
<string name="common_explorer">エクスプローラー</string>
|
||||
<string name="common_fee_label">手数料</string>
|
||||
<string name="common_fee_selector_footer">ネットワーク手数料は、取引の処理と確認のためにユーザーが支払う料金です。手数料の額は、ネットワークの混雑さ、取引のサイズ、実行の優先度によって左右されます。 %s</string>
|
||||
<string name="common_fee_selector_option_fast">速い</string>
|
||||
<string name="common_fee_selector_option_market">マーケット</string>
|
||||
|
|
@ -111,6 +111,9 @@
|
|||
<string name="common_go_to_provider">プロバイダーへ移動</string>
|
||||
<string name="common_go_to_token">トークンへ移動</string>
|
||||
<string name="common_import">インポート</string>
|
||||
<plurals name="common_in_days">
|
||||
<item quantity="other">%d日で</item>
|
||||
</plurals>
|
||||
<string name="common_later">後で</string>
|
||||
<string name="common_locked">ロックされています</string>
|
||||
<string name="common_main_network">メインネットワーク</string>
|
||||
|
|
@ -166,7 +169,9 @@
|
|||
<string name="custom_token_contract_address_input_title">コントラクトアドレス</string>
|
||||
<string name="custom_token_creation_error_invalid_contract_address">コントラクトアドレスが無効です</string>
|
||||
<string name="custom_token_creation_error_network_not_selected">ネットワークを選択してください</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">小数は%liまでの有効な整数である必要があります</string>
|
||||
<string name="custom_token_creation_error_token_already_exist_message">このトークンはすでにリストに追加されています</string>
|
||||
<string name="custom_token_creation_error_token_already_exist_title">トークンはすでに存在します</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">小数は%dまでの有効な整数である必要があります</string>
|
||||
<string name="custom_token_custom_derivation">カスタム派生パス</string>
|
||||
<string name="custom_token_custom_derivation_placeholder">例:m/00\'/0000\'/0\'/0/0</string>
|
||||
<string name="custom_token_custom_derivation_title">カスタム派生パスを入力</string>
|
||||
|
|
@ -332,15 +337,17 @@
|
|||
<string name="manage_tokens_unavailable_vote">賛成票を投じる</string>
|
||||
<string name="manage_tokens_wallet_selector_title">ウォレットを選択</string>
|
||||
<string name="manage_tokens_wallet_support_only_one_network_title">ウォレットは複数のネットワークをサポートしていません。</string>
|
||||
<string name="markets_add_to_my_portfolio_description">このアセットの購入、交換、受け取りを開始するには、このトークンを少なくとも1つのネットワークに追加してください。</string>
|
||||
<string name="markets_add_to_my_portfolio_description">このアセットの購入、交換、受け取りを開始するには、ポートフォリオに追加してください。</string>
|
||||
<string name="markets_add_to_my_portfolio_unavailable_description">このアセットは利用できません</string>
|
||||
<string name="markets_add_to_portfolio_button">ポートフォリオに追加</string>
|
||||
<string name="markets_add_token">トークンを追加</string>
|
||||
<string name="markets_add_token">追加</string>
|
||||
<string name="markets_available_networks">利用可能なネットワーク</string>
|
||||
<string name="markets_common_my_portfolio">私のポートフォリオ</string>
|
||||
<string name="markets_common_title">マーケット</string>
|
||||
<string name="markets_generate_addresses_notification">選択したネットワークのアドレスを生成するには、Tangemカードをスキャンする必要があります。</string>
|
||||
<string name="markets_insights_info_description_message">このセクションのデータは、次のネットワークから取得されています: %s</string>
|
||||
<string name="markets_loading_error_title">データを読み込めません…</string>
|
||||
<string name="markets_loading_no_data_title">データなし</string>
|
||||
<string name="markets_quick_actions">クイックアクション</string>
|
||||
<string name="markets_search_result_title">結果</string>
|
||||
<string name="markets_search_see_tokens_under_100k">時価総額10万ドル以下のトークンを見る</string>
|
||||
|
|
@ -365,7 +372,7 @@
|
|||
<plurals name="markets_token_details_based_on_ratings">
|
||||
<item quantity="other">%d のレーティングに基づいて</item>
|
||||
</plurals>
|
||||
<string name="markets_token_details_blockchain_site">ブロックチェーンサイト</string>
|
||||
<string name="markets_token_details_blockchain_site">ウェブサイト</string>
|
||||
<string name="markets_token_details_buy_pressure">買い圧力</string>
|
||||
<string name="markets_token_details_buy_pressure_description">買い手と売り手の取引量の差</string>
|
||||
<string name="markets_token_details_circulating_supply">循環供給量</string>
|
||||
|
|
@ -446,7 +453,7 @@
|
|||
</plurals>
|
||||
<string name="onboarding_seed_generate_title">あなたのシードフレーズ</string>
|
||||
<plurals name="onboarding_seed_generate_words_count">
|
||||
<item quantity="other"> %d 単語</item>
|
||||
<item quantity="other">%d 単語</item>
|
||||
</plurals>
|
||||
<string name="onboarding_seed_import_message">ウォレットをインポートするには、下のフィールドにシードフレーズを入力してください。</string>
|
||||
<string name="onboarding_seed_intro_button_generate">シードフレーズを生成する</string>
|
||||
|
|
@ -497,7 +504,7 @@
|
|||
<string name="qr_scanner_camera_denied_title">カメラへのアクセスが拒否されました</string>
|
||||
<string name="receive_bottom_sheet_warning_message">%3$sネットワーク上の%1$s ( %2$s )</string>
|
||||
<string name="receive_bottom_sheet_warning_message_full">このアドレスには%sのみを送金してください。他のトークンを送信すると、取り返しのつかない損失が発生します。</string>
|
||||
<string name="receive_token_description">QRコードを表示するか、アドレスを共有します</string>
|
||||
<string name="receive_token_description">QRコードを表示するか、アドレスを共有してください</string>
|
||||
<string name="referral_button_participate">参加する</string>
|
||||
<string name="referral_error_failed_to_load_info">紹介プログラムに関する情報を読み込めませんでした。しばらくしてからもう一度お試しください。</string>
|
||||
<string name="referral_error_failed_to_load_info_with_reason">紹介プログラムに関する情報を読み込めませんでした。エラー コード: %s 。しばらくしてからもう一度お試しください。</string>
|
||||
|
|
@ -559,18 +566,11 @@
|
|||
<string name="send_custom_kaspa_per_utxo_footer">Kaspaネットワークで未使用の取引出力(UTXO)を使用するために必要な手数料です。取引で使用するUTXOが多ければ多いほど、手数料は高くなります。</string>
|
||||
<string name="send_custom_kaspa_per_utxo_title">UTXOあたりのKAS</string>
|
||||
<string name="send_date_format">%1$s 、 %2$s</string>
|
||||
<string name="send_destination_hint_address">アドレス</string>
|
||||
<string name="send_destination_tag_field">宛先タグ</string>
|
||||
<string name="send_enter_address_field">アドレスを入力</string>
|
||||
<string name="send_error_address_same_as_wallet">アドレスはウォレットアドレスと同じです</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">無効なタグです。取引には追加されません。</string>
|
||||
<string name="send_extras_error_invalid_memo">無効なメモです。取引には追加されません。</string>
|
||||
<string name="send_extras_hint_destination_tag">タグ</string>
|
||||
<string name="send_extras_hint_memo">メモ</string>
|
||||
<string name="send_fee_include_description">手数料込み</string>
|
||||
<string name="send_fee_picker_low">低い</string>
|
||||
<string name="send_fee_picker_normal">普通</string>
|
||||
<string name="send_fee_picker_priority">優先</string>
|
||||
<string name="send_fee_unreachable_error_text">ネットワーク接続を確認してください</string>
|
||||
<string name="send_fee_unreachable_error_title">ネットワーク手数料についての情報にアクセスできません</string>
|
||||
<string name="send_from_wallet_android">より</string>
|
||||
|
|
@ -585,7 +585,7 @@
|
|||
<string name="send_network_fee_warning_title">ネットワーク手数料のカバー</string>
|
||||
<string name="send_notification_exceed_balance_text">手数料と送金額の合計が残高を超えているため、送金に必要な資金が不足しています。</string>
|
||||
<string name="send_notification_exceed_balance_title">合計が残高を超えています</string>
|
||||
<string name="send_notification_existential_deposit_text">残高が最低量を下回ると、当アカウントはブロックチェーンから消去されます。残高に%sを残しておいてください。</string>
|
||||
<string name="send_notification_existential_deposit_text">セキュリティリスクを防ぐため、ブロックチェーン上にアカウントを維持するには、少なくとも%s の残高が必要です。この金額は残高に残り、引き出すことはできません。</string>
|
||||
<string name="send_notification_existential_deposit_title">アカウント維持に必要な最低残高</string>
|
||||
<string name="send_notification_fee_too_high_text">手数料額が推奨額の%s倍となっています。カスタム設定が正しいことを再度確認してください。</string>
|
||||
<string name="send_notification_fee_too_high_title">カスタム手数料が高くなっています</string>
|
||||
|
|
@ -619,21 +619,15 @@
|
|||
<string name="send_summary_title">%sを送金する</string>
|
||||
<string name="send_summary_transaction_description">**%1$s** を送金する (ネットワーク手数料%2$sを含む)</string>
|
||||
<string name="send_summary_transaction_description_no_fiat_fee">**%1$s** と %2$s を送金しています。</string>
|
||||
<string name="send_title_currency_format">%sを送信しています</string>
|
||||
<string name="send_total_label">合計</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$sと%2$sが送信されます</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (%2$s: 手数料を含む)</string>
|
||||
<string name="send_total_subtitle_format">%sが送信されます</string>
|
||||
<string name="send_transaction_success">取引は正常に署名され、ブロックチェーンノードに送信されました。ウォレットの残高はしばらくして更新されます。</string>
|
||||
<string name="send_tron_account_activation_error">%1$sはTronネットワークのアセットです。手数料を計算して取引を行うには、アカウントにTron(TRX)を入金する必要があります。</string>
|
||||
<string name="send_validation_invalid_address">無効なアドレス</string>
|
||||
<string name="sent_transaction_sent_title">取引が送信されました</string>
|
||||
<string name="settings_card_settings_footer">セットアップしたいカードをスキャンするために準備してください。</string>
|
||||
<string name="settings_forget_wallet">ウォレット削除</string>
|
||||
<string name="settings_forget_wallet_footer">これにより、ウォレットがアプリから削除されます。ウォレットは再度追加できます。</string>
|
||||
<string name="settings_wallet_name_title">名前</string>
|
||||
<string name="staking_active">アクティブ</string>
|
||||
<string name="staking_active_footer">資産のステーキングを解除するには、ここをクリックしてください。</string>
|
||||
<string name="staking_active_footer">資産のステーキングを解除するには、上のブロックをタップしてください</string>
|
||||
<string name="staking_amount_requirement_error">ステーキング金額は %s 以上である必要があります</string>
|
||||
<string name="staking_claim_unstaked">ステーキング解除分を請求する</string>
|
||||
<string name="staking_details_annual_percentage_rate">年率</string>
|
||||
|
|
@ -646,25 +640,27 @@
|
|||
<string name="staking_details_market_rating">市場評価</string>
|
||||
<string name="staking_details_metrics_block_header">指標</string>
|
||||
<string name="staking_details_minimum_requirement">最低要件</string>
|
||||
<string name="staking_details_no_rewards_to_claim">請求できる報酬はありません</string>
|
||||
<string name="staking_details_no_rewards_to_claim">報酬なし</string>
|
||||
<string name="staking_details_reward_claiming">請求中の報酬</string>
|
||||
<string name="staking_details_reward_claiming_info">ステーキング報酬を受け取る方法。自動または手動で請求できます。</string>
|
||||
<string name="staking_details_reward_claiming_info">ステーキング報酬の受け取り方法。\n自動であなたのアドレスに報酬が入金される方法と、手動で取引を生成して報酬を引き出す方法があります。</string>
|
||||
<string name="staking_details_reward_schedule">報酬スケジュール</string>
|
||||
<string name="staking_details_reward_schedule_info">これは、ステーキングの参加者がいつ報酬を受け取るかを決定するスケジュールです。</string>
|
||||
<string name="staking_details_rewards_to_claim">受け取る報酬: %s</string>
|
||||
<string name="staking_details_rewards_to_claim">報酬: %s</string>
|
||||
<string name="staking_details_title">ステーキング%s</string>
|
||||
<string name="staking_details_unbonding_period">解約完了までの期間</string>
|
||||
<string name="staking_details_unbonding_period_info">ステーキングから資金の引き出しを要求した後、トークンが利用可能になるまでの待機期間。</string>
|
||||
<string name="staking_details_warmup_period">ウォームアップ期間</string>
|
||||
<string name="staking_details_warmup_period_info">ステーキングへの参加を有効にするために割り当てられた時間。</string>
|
||||
<string name="staking_locked">ロック中</string>
|
||||
<string name="staking_migrate">移行</string>
|
||||
<string name="staking_native">ネイティブステーキング</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_day">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎日受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_hour">ステーキングにより%1$sを獲得できます。ステーキング報酬は1時間ごとに受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_hour">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎時間受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_month">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎月受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_week">ステーキングにより%1$sを獲得できます。ステーキング報酬は毎週受け取れます。</string>
|
||||
<string name="staking_notification_earn_rewards_title">ステーキング報酬を獲得</string>
|
||||
<string name="staking_notification_unstake_text">ステーキング解除後、報酬の獲得はすぐに停止します。ステーキング解除プロセスには%sかかります。</string>
|
||||
<string name="staking_notification_unstake_text">ステーキング解除プロセスの開始後、報酬の獲得はすぐに停止します。ステーキング解除には%sかかります。</string>
|
||||
<string name="staking_ready_to_withdraw">引き出し準備完了</string>
|
||||
<string name="staking_rebond">再結束</string>
|
||||
<string name="staking_restake">再度ステーキングする</string>
|
||||
<string name="staking_restake_rewards">報酬をステーキングする</string>
|
||||
|
|
@ -685,8 +681,9 @@
|
|||
<string name="staking_stake_more">もっとステーキングする</string>
|
||||
<string name="staking_title_stake">%sをステーキングする</string>
|
||||
<string name="staking_title_unstake">%sのステーキング解除</string>
|
||||
<string name="staking_unbonding">ステーキング解約中</string>
|
||||
<string name="staking_unlocked_locked">ステーキング解除はロックされています</string>
|
||||
<string name="staking_unstaked">スタックされていない</string>
|
||||
<string name="staking_unstaked">ステーキングされていない</string>
|
||||
<string name="staking_unstaked_footer">資産を請求するために、unstakedを確認してください</string>
|
||||
<string name="staking_unstaking">ステーキング解除</string>
|
||||
<string name="staking_validator">バリデーター</string>
|
||||
|
|
@ -822,6 +819,8 @@
|
|||
<string name="wallet_settings_title">ウォレット設定</string>
|
||||
<string name="wallet_title">Tangem</string>
|
||||
<string name="warning_access_denied_message">%sを使用するか、カードをスキャンしてウォレットにアクセスしてください</string>
|
||||
<string name="warning_approval_in_progress_message">許可付与のプロセスは現在進行中であり、まもなく完了する予定です。</string>
|
||||
<string name="warning_approval_in_progress_title">承認中</string>
|
||||
<string name="warning_backup_errors_message">カードのアクティベーションが正しく完了しませんでした。デバイスの NFCモジュールに問題があるか、カードをデバイスに正しくタップしていないことが原因かもしれません。サポートチームにお問い合わせください。</string>
|
||||
<string name="warning_backup_errors_title">アクティベーションに失敗しました</string>
|
||||
<string name="warning_beacon_chain_retirement_content">BNBネットワーク開発者によると、BEP-2規格のサポートは2024年6月に終了します。この規格の資産を失わないために、BEP-20規格に変換してください。BNBスマートチェーンネットワークへ移行するには、Tangemのスワップサービスをご利用ください。</string>
|
||||
|
|
@ -839,7 +838,7 @@
|
|||
<string name="warning_existential_deposit_title">ネットワークには最低残高が必要です</string>
|
||||
<string name="warning_express_active_transaction_message">スワップは、%s の取引完了後に利用可能となります。</string>
|
||||
<string name="warning_express_active_transaction_title">アクティブな取引があります</string>
|
||||
<string name="warning_express_approval_in_progress_message">スワップの承認は現在進行中で、まもなく完了する予定です。</string>
|
||||
<string name="warning_express_approval_in_progress_message">スワップ承認は現在進行中で、まもなく完了する予定です。</string>
|
||||
<string name="warning_express_approval_in_progress_title">承認が進行中</string>
|
||||
<string name="warning_express_dust_message">最低のスワップ金額は%1$s です。スワップ後の残金が%2$s を下回らないようにしてください。</string>
|
||||
<string name="warning_express_no_exchangeable_coins_description">あなたのリストには、交換可能な %s トークンがありません。</string>
|
||||
|
|
@ -863,8 +862,8 @@
|
|||
<string name="warning_low_signatures_message">このカードには%sの署名のみが残っています。資金をすべて引き出す必要があります。</string>
|
||||
<string name="warning_low_signatures_title">署名数が少ないです</string>
|
||||
<string name="warning_manage_tokens_legacy_derivation_message">異なるネットワーク上のトークンは、異なるアドレスを持つ場合があります。資金を送金する際には、アドレスがネットワークと一致していることを再確認してください。</string>
|
||||
<string name="warning_matic_migration_title">MATICからPOLへの移行</string>
|
||||
<string name="warning_matic_migration_message">MATICはPOLに移行中です。ただし、期限は設定されておらず、MATICはまだ廃止されていません。MATICトークンを引き続き安全に使用することも、取引所でPOLに交換することもできます。</string>
|
||||
<string name="warning_matic_migration_title">MATICからPOLへの移行</string>
|
||||
<plurals name="warning_missing_derivation_message">
|
||||
<item quantity="other">%d ネットワークのアドレスを取得するために、カードを利用してください</item>
|
||||
</plurals>
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@
|
|||
<string name="common_access_denied">Доступ запрещен</string>
|
||||
<string name="common_all">Все</string>
|
||||
<string name="common_allow">Разрешить</string>
|
||||
<string name="common_analytics">Аналитика</string>
|
||||
<string name="common_apply">Применить</string>
|
||||
<string name="common_approval">Одобрение</string>
|
||||
<string name="common_approve">Подтвердить</string>
|
||||
|
|
@ -107,7 +108,6 @@
|
|||
<string name="common_explore">Обозреватель</string>
|
||||
<string name="common_explore_transaction_history">Посмотреть историю транзакций</string>
|
||||
<string name="common_explorer">Обозреватель</string>
|
||||
<string name="common_fee_label">Комиссия</string>
|
||||
<string name="common_fee_selector_footer">Сетевые комиссии – это плата пользователя за обработку и подтверждение транзакций. Размер комиссии зависит от нагрузки на сеть, объема транзакции и приоритета исполнения. %s</string>
|
||||
<string name="common_fee_selector_option_fast">Быстро</string>
|
||||
<string name="common_fee_selector_option_market">По рынку</string>
|
||||
|
|
@ -117,6 +117,12 @@
|
|||
<string name="common_go_to_provider">К провайдеру</string>
|
||||
<string name="common_go_to_token">Перейти в токен</string>
|
||||
<string name="common_import">Импортировать</string>
|
||||
<plurals name="common_in_days">
|
||||
<item quantity="one">через %d день</item>
|
||||
<item quantity="few">через %d дня</item>
|
||||
<item quantity="many">через %d дней</item>
|
||||
<item quantity="other">через %d дней</item>
|
||||
</plurals>
|
||||
<string name="common_later">Позже</string>
|
||||
<string name="common_locked">Заблокирован</string>
|
||||
<string name="common_main_network">Основная сеть</string>
|
||||
|
|
@ -172,7 +178,7 @@
|
|||
<string name="custom_token_contract_address_input_title">Адрес контракта</string>
|
||||
<string name="custom_token_creation_error_invalid_contract_address">Адрес контракта некорректен</string>
|
||||
<string name="custom_token_creation_error_network_not_selected">Пожалуйста, выберите сеть</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Десятичное число должно быть действительным целым числом, до %li</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Десятичное число должно быть действительным целым числом, до %d</string>
|
||||
<string name="custom_token_custom_derivation">Своя деривация</string>
|
||||
<string name="custom_token_custom_derivation_placeholder">Например m/00\'/0000\'/0\'/0/0</string>
|
||||
<string name="custom_token_custom_derivation_title">Введите свою деривацию</string>
|
||||
|
|
@ -347,6 +353,8 @@
|
|||
<string name="markets_common_my_portfolio">Мой портфель</string>
|
||||
<string name="markets_common_title">Рынок</string>
|
||||
<string name="markets_generate_addresses_notification">Чтобы создать адреса для выбранных сетей, отсканируйте вашу карту Tangem кошелька</string>
|
||||
<string name="markets_insights_info_description_message">Данные раздела получены из следующих сетей: %s</string>
|
||||
<string name="markets_loading_no_data_title">Нет данных</string>
|
||||
<string name="markets_quick_actions">Быстрые действия</string>
|
||||
<string name="markets_search_result_title">Результат</string>
|
||||
<string name="markets_search_see_tokens_under_100k">Токены с капитализацией меньше 100к</string>
|
||||
|
|
@ -354,8 +362,8 @@
|
|||
<string name="markets_search_token_no_result_title">Нет результата</string>
|
||||
<string name="markets_select_network">Выберите сеть</string>
|
||||
<string name="markets_select_wallet">Выберите кошелек</string>
|
||||
<string name="markets_selector_interval_1m_title">1мин</string>
|
||||
<string name="markets_selector_interval_1y_title">1год</string>
|
||||
<string name="markets_selector_interval_1m_title">1м</string>
|
||||
<string name="markets_selector_interval_1y_title">1г</string>
|
||||
<string name="markets_selector_interval_24h_title">24ч</string>
|
||||
<string name="markets_selector_interval_3m_title">3м</string>
|
||||
<string name="markets_selector_interval_6m_title">6м</string>
|
||||
|
|
@ -374,7 +382,7 @@
|
|||
<item quantity="many">На основе %d оценок</item>
|
||||
<item quantity="other">На основе %d оценок</item>
|
||||
</plurals>
|
||||
<string name="markets_token_details_blockchain_site">Сайт блокчейна</string>
|
||||
<string name="markets_token_details_blockchain_site">Веб-сайт</string>
|
||||
<string name="markets_token_details_buy_pressure">Покупательское предпочтение</string>
|
||||
<string name="markets_token_details_buy_pressure_description">Разница между объемом покупателей и продавцов</string>
|
||||
<string name="markets_token_details_circulating_supply">Циркулирующее предложение</string>
|
||||
|
|
@ -577,18 +585,11 @@
|
|||
<string name="send_custom_kaspa_per_utxo_footer">Комиссия, которую нужно заплатить за использование каждого неиспользованного выхода транзакции (UTXO) в сети Kaspa. Чем больше UTXO вы используете в транзакции, тем выше будет комиссия.</string>
|
||||
<string name="send_custom_kaspa_per_utxo_title">KAS за UTXO</string>
|
||||
<string name="send_date_format">%1$s, %2$s</string>
|
||||
<string name="send_destination_hint_address">Адрес</string>
|
||||
<string name="send_destination_tag_field">Код назначения</string>
|
||||
<string name="send_enter_address_field">Введите адрес</string>
|
||||
<string name="send_error_address_same_as_wallet">Адрес совпадает с адресом кошелька</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Недопустимый Tag. Он не будет добавлен в транзакцию.</string>
|
||||
<string name="send_extras_error_invalid_memo">Недопустимый Memo. Он не будет добавлен в транзакцию.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">Включая комиссию</string>
|
||||
<string name="send_fee_picker_low">Низкая</string>
|
||||
<string name="send_fee_picker_normal">Нормальная</string>
|
||||
<string name="send_fee_picker_priority">Приоритетная</string>
|
||||
<string name="send_fee_unreachable_error_text">Проверьте своё интернет соединение</string>
|
||||
<string name="send_fee_unreachable_error_title">Информация о комиссии сети недоступна</string>
|
||||
<string name="send_from_wallet_android">Из</string>
|
||||
|
|
@ -603,7 +604,7 @@
|
|||
<string name="send_network_fee_warning_title">Покрытие сетевой комиссии</string>
|
||||
<string name="send_notification_exceed_balance_text">Недостаточно средств для перевода, так как сумма комиссии и сумма перевода в совокупности больше имеющегося баланса</string>
|
||||
<string name="send_notification_exceed_balance_title">Недостаточно средств</string>
|
||||
<string name="send_notification_existential_deposit_text">Аккаунт будет удален из блокчейна, если баланс упадет ниже экзистенциального депозита. Пожалуйста, оставьте %s на балансе.</string>
|
||||
<string name="send_notification_existential_deposit_text">Для сохранения вашего аккаунта в блокчейне и защиты от возможных рисков необходим баланс не менее %s. Эта сумма останется на вашем счете и не может быть снята.</string>
|
||||
<string name="send_notification_existential_deposit_title">Экзистенциальный депозит</string>
|
||||
<string name="send_notification_fee_too_high_text">Сумма комиссии в %s раз превышает рекомендованную. Убедитесь, что указанная комиссия верна.</string>
|
||||
<string name="send_notification_fee_too_high_title">Установлена высокая комиссия</string>
|
||||
|
|
@ -637,24 +638,18 @@
|
|||
<string name="send_summary_title">Отправка %s</string>
|
||||
<string name="send_summary_transaction_description">Вы отправляете **%1$s**, включая комиссию сети %2$s</string>
|
||||
<string name="send_summary_transaction_description_no_fiat_fee">Вы отправляете **%1$s** и %2$s</string>
|
||||
<string name="send_title_currency_format">Отправка %s</string>
|
||||
<string name="send_total_label">Всего</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s и %2$s будет отправлено</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (вкл. комиссию: %2$s)</string>
|
||||
<string name="send_total_subtitle_format">%s будет отправлено</string>
|
||||
<string name="send_transaction_success">Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время</string>
|
||||
<string name="send_tron_account_activation_error">%1$s — это монета в сети Tron. Чтобы рассчитать комиссию и совершить транзакцию, вам необходимо внести немного Tron (TRX) на свой адрес.</string>
|
||||
<string name="send_validation_invalid_address">Неверный адрес</string>
|
||||
<string name="sent_transaction_sent_title">Транзакция отправлена</string>
|
||||
<string name="settings_card_settings_footer">Подготовьтесь к сканированию карты, которую вы хотите настроить.</string>
|
||||
<string name="settings_forget_wallet">Забыть кошелек</string>
|
||||
<string name="settings_forget_wallet_footer">Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова.</string>
|
||||
<string name="settings_wallet_name_title">Имя</string>
|
||||
<string name="staking_active">Активно</string>
|
||||
<string name="staking_active_footer">Для завершения стейкинга нажмите сюда</string>
|
||||
<string name="staking_active_footer">Для завершения стейкинга нажмите на блок выше</string>
|
||||
<string name="staking_amount_requirement_error">Сумма для стейкинга должна быть не менее %s</string>
|
||||
<string name="staking_claim_unstaked">Забрать средства</string>
|
||||
<string name="staking_details_annual_percentage_rate">Годовая процентная ставка</string>
|
||||
<string name="staking_details_annual_percentage_rate">Процентная ставка</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">Годовой процентный доход, который вы можете получить от участия в стейкинге.</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
<string name="staking_details_available">Доступно</string>
|
||||
|
|
@ -675,6 +670,7 @@
|
|||
<string name="staking_details_unbonding_period_info">Период, который необходимо подождать после запроса на вывод средств из стейкинга, прежде чем токены станут доступны.</string>
|
||||
<string name="staking_details_warmup_period">Период прогрева</string>
|
||||
<string name="staking_details_warmup_period_info">Время, необходимое для начала процесса стейкинга и активации процесса начисления наград</string>
|
||||
<string name="staking_locked">Заблокировано</string>
|
||||
<string name="staking_migrate">Переместить</string>
|
||||
<string name="staking_native">Нативный стейкинг</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_day">Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый день.</string>
|
||||
|
|
@ -682,12 +678,12 @@
|
|||
<string name="staking_notification_earn_rewards_text_period_month">Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый месяц.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_week">Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждую неделю.</string>
|
||||
<string name="staking_notification_earn_rewards_title">Получите награду за стейкинг</string>
|
||||
<string name="staking_notification_unstake_text">Награда перестанет начисляться сразу после завершения стейкинга. Процесс завершения длится %s.</string>
|
||||
<string name="staking_restake">Повторный стейкинг</string>
|
||||
<string name="staking_notification_unstake_text">Награда перестанет начисляться сразу после того, как вы начнете процесс завершения стейкинга. Процесс завершения длится %s.</string>
|
||||
<string name="staking_restake">Сменить валидатора</string>
|
||||
<string name="staking_restake_rewards">Застейкать вознаграждения</string>
|
||||
<string name="staking_revoke">Отозвать</string>
|
||||
<string name="staking_revote">Переголосовать</string>
|
||||
<string name="staking_reward_claiming_auto">Автоматически</string>
|
||||
<string name="staking_reward_claiming_auto">Авто</string>
|
||||
<string name="staking_reward_claiming_manual">Вручную</string>
|
||||
<string name="staking_reward_schedule_block">Блок</string>
|
||||
<string name="staking_reward_schedule_day">День</string>
|
||||
|
|
@ -701,8 +697,9 @@
|
|||
<string name="staking_stake_locked">Стейкинг закрыт</string>
|
||||
<string name="staking_stake_more">Застейкать еще</string>
|
||||
<string name="staking_title_stake">Застейкать %s</string>
|
||||
<string name="staking_title_unstake">Вывести %s</string>
|
||||
<string name="staking_unlocked_locked">Разблокировать</string>
|
||||
<string name="staking_unstaked">Выведено из стейкинга</string>
|
||||
<string name="staking_unstaked">Вывод из стейкинга</string>
|
||||
<string name="staking_unstaked_footer">Проверьте процесс завершения стейкинга, чтобы вывести свои средства.</string>
|
||||
<string name="staking_unstaking">Завершение стейкинга</string>
|
||||
<string name="staking_validator">Валидатор</string>
|
||||
|
|
@ -882,8 +879,8 @@
|
|||
<string name="warning_low_signatures_message">На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства.</string>
|
||||
<string name="warning_low_signatures_title">Малое количество подписей</string>
|
||||
<string name="warning_manage_tokens_legacy_derivation_message">Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети.</string>
|
||||
<string name="warning_matic_migration_title">Миграция MATIC в POL</string>
|
||||
<string name="warning_matic_migration_message">Токен MATIC мигрирует на POL. Однако крайний срок не установлен, и MATIC пока не устарел. Вы можете спокойно продолжать использовать токен MATIC или воспользоваться биржами, чтобы обменять его на POL.</string>
|
||||
<string name="warning_matic_migration_title">Миграция MATIC в POL</string>
|
||||
<plurals name="warning_missing_derivation_message">
|
||||
<item quantity="one">Используйте вашу карту, чтобы получить адрес для %d сети</item>
|
||||
<item quantity="few">Используйте вашу карту, чтобы получить адреса для %d сетей</item>
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@
|
|||
<string name="common_attention">Увага</string>
|
||||
<string name="common_balance">Баланс: %s</string>
|
||||
<string name="common_balance_title">Баланс</string>
|
||||
<string name="common_biometric_authentication">біометрична автентифікація</string>
|
||||
<string name="common_biometrics">біометрією</string>
|
||||
<string name="common_biometric_authentication">біометричну автентифікацію</string>
|
||||
<string name="common_biometrics">біометрії</string>
|
||||
<string name="common_buy">Купити</string>
|
||||
<string name="common_buy_currency">Перейдіть до %1$s</string>
|
||||
<string name="common_camera_denied_alert_message">Ви не надали доступ до камери, будь ласка, змініть налаштування конфіденційності</string>
|
||||
|
|
@ -107,7 +107,6 @@
|
|||
<string name="common_explore">Оглядач</string>
|
||||
<string name="common_explore_transaction_history">Переглянути історію транзакцій</string>
|
||||
<string name="common_explorer">Оглядач</string>
|
||||
<string name="common_fee_label">Комісія</string>
|
||||
<string name="common_fee_selector_footer">Мережеві комісії – це збори, які користувачі сплачують за обробку та підтвердження транзакцій. На розмір комісії може впливати перевантаження мережі, розмір транзакції та пріоритет виконання. %s</string>
|
||||
<string name="common_fee_selector_option_fast">Швидко</string>
|
||||
<string name="common_fee_selector_option_market">За ринком</string>
|
||||
|
|
@ -152,7 +151,7 @@
|
|||
<string name="common_stake">Застейкати</string>
|
||||
<string name="common_staking">Стейкінг</string>
|
||||
<string name="common_start">Почати</string>
|
||||
<string name="common_submit">Надіслати</string>
|
||||
<string name="common_submit">Продовжити</string>
|
||||
<string name="common_success">Успіх</string>
|
||||
<string name="common_support">Підтримка</string>
|
||||
<string name="common_swap">Обмін</string>
|
||||
|
|
@ -172,7 +171,7 @@
|
|||
<string name="custom_token_contract_address_input_title">Адреса контракту</string>
|
||||
<string name="custom_token_creation_error_invalid_contract_address">Адреса контракту недійсна</string>
|
||||
<string name="custom_token_creation_error_network_not_selected">Будь ласка, оберіть мережу</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Десяткове число повинно бути дійсним цілим числом, до %li</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Десяткове число повинно бути дійсним цілим числом, до %d</string>
|
||||
<string name="custom_token_custom_derivation">Власна деривація</string>
|
||||
<string name="custom_token_custom_derivation_placeholder">Наприклад, m/00\'/0000\'/0\'/0/0</string>
|
||||
<string name="custom_token_custom_derivation_title">Введіть власну деривацію</string>
|
||||
|
|
@ -377,7 +376,7 @@
|
|||
<item quantity="many">На основі %d оцінок</item>
|
||||
<item quantity="other">На основі %d оцінок</item>
|
||||
</plurals>
|
||||
<string name="markets_token_details_blockchain_site">Блокчейн сайт</string>
|
||||
<string name="markets_token_details_blockchain_site">Веб-сайт</string>
|
||||
<string name="markets_token_details_buy_pressure">Давлення покупця</string>
|
||||
<string name="markets_token_details_buy_pressure_description">Різниця між обсягом покупців та обсягом продавців</string>
|
||||
<string name="markets_token_details_circulating_supply">Циркуляційний запас</string>
|
||||
|
|
@ -473,7 +472,7 @@
|
|||
<string name="onboarding_seed_intro_title">Використовувати seed-фразу</string>
|
||||
<string name="onboarding_seed_mnemonic_invalid_checksum">Невірна seed-фраза. Будь ласка, перевірте порядок слів.</string>
|
||||
<string name="onboarding_seed_mnemonic_wrong_words">Невірна seed-фраза. Будь ласка, перевірте правопис.</string>
|
||||
<string name="onboarding_seed_phrase_intro_legacy">Застарілий</string>
|
||||
<string name="onboarding_seed_phrase_intro_legacy">Застаріло</string>
|
||||
<string name="onboarding_seed_user_validation_message">Щоб перевірити чи правильно ви записали seed-фразу, введіть 2-е, 7-е та 11-те слова</string>
|
||||
<string name="onboarding_seed_user_validation_title">Отже, давайте перевіримо</string>
|
||||
<string name="onboarding_subtitle_no_backup_cards">Щоб почати процес резервного копіювання, додайте одну або дві резервні картки.</string>
|
||||
|
|
@ -530,7 +529,7 @@
|
|||
<item quantity="many">за %d гаманців</item>
|
||||
<item quantity="other">за %d гаманців</item>
|
||||
</plurals>
|
||||
<string name="referral_point_currencies_description">Отримайте ^^%1$s^^ на вашу адресу в мережі %2$s%3$s ^^через 30 днів^^ за кожен гаманець, який придбає ваш друг</string>
|
||||
<string name="referral_point_currencies_description">Отримаєте ^^%1$s^^ на вашу адресу в мережі %2$s %3$s ^^через 30 днів^^ за кожен гаманець, який придбає ваш друг</string>
|
||||
<string name="referral_point_currencies_title">Ви</string>
|
||||
<string name="referral_point_discount_description_prefix">Отримає</string>
|
||||
<string name="referral_point_discount_description_suffix">при купівлі гаманця на сайті tangem.com</string>
|
||||
|
|
@ -583,18 +582,11 @@
|
|||
<string name="send_custom_kaspa_per_utxo_footer">Комісія, необхідна за використання кожної невитраченої транзакції (UTXO) у мережі Kaspa. Чим більше UTXO ви використовуєте в транзакції, тим вищою буде комісія.</string>
|
||||
<string name="send_custom_kaspa_per_utxo_title">KAS за UTXO</string>
|
||||
<string name="send_date_format">%1$s, %2$s</string>
|
||||
<string name="send_destination_hint_address">Адреса</string>
|
||||
<string name="send_destination_tag_field">Тег призначення</string>
|
||||
<string name="send_enter_address_field">Введіть адресу</string>
|
||||
<string name="send_error_address_same_as_wallet">Адреса збігається з адресою гаманця</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Недопустимий Tag. Він не буде доданий у транзакцію.</string>
|
||||
<string name="send_extras_error_invalid_memo">Недопустимий Memo. Він не буде доданий до транзакції.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">Включаючи комісію</string>
|
||||
<string name="send_fee_picker_low">Низька</string>
|
||||
<string name="send_fee_picker_normal">Нормальна</string>
|
||||
<string name="send_fee_picker_priority">Пріоритетна</string>
|
||||
<string name="send_fee_unreachable_error_text">Перевірте підключення до мережі</string>
|
||||
<string name="send_fee_unreachable_error_title">Інформація щодо комісії в мережі недоступна</string>
|
||||
<string name="send_from_wallet_android">Із</string>
|
||||
|
|
@ -609,7 +601,7 @@
|
|||
<string name="send_network_fee_warning_title">Покриття мережевої комісії</string>
|
||||
<string name="send_notification_exceed_balance_text">Недостатньо коштів для здійснення переказу, оскільки загальна сума комісії та переказу перевищує наявний баланс</string>
|
||||
<string name="send_notification_exceed_balance_title">Сума перевищує баланс</string>
|
||||
<string name="send_notification_existential_deposit_text">Рахунок буде видалено з блокчейну, якщо баланс стане нижчим за екзистенційний депозит. Будь ласка, залиште %s на своєму балансі.</string>
|
||||
<string name="send_notification_existential_deposit_text">Для збереження вашого акаунту у блокчейні та захисту від можливих ризиків необхідний баланс не менше %s. Ця сума залишиться на вашому рахунку та не може бути знята.</string>
|
||||
<string name="send_notification_existential_deposit_title">Екзистенційний депозит</string>
|
||||
<string name="send_notification_fee_too_high_text">Сума комісії в %s разів перевищує рекомендовану. Переконайтеся, що користувацькі налаштування вірні.</string>
|
||||
<string name="send_notification_fee_too_high_title">Встановлена комісія завелика</string>
|
||||
|
|
@ -643,23 +635,18 @@
|
|||
<string name="send_summary_title">Надіслати %s</string>
|
||||
<string name="send_summary_transaction_description">Ви надсилаєте **%1$s**, включно з комісію мережі %2$s</string>
|
||||
<string name="send_summary_transaction_description_no_fiat_fee">Ви надсилаєте **%1$s** і %2$s</string>
|
||||
<string name="send_title_currency_format">Надсилання %s</string>
|
||||
<string name="send_total_label">Всього</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s та %2$s буде надіслано</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (вкл. комісію: %2$s )</string>
|
||||
<string name="send_total_subtitle_format">%s буде надіслано</string>
|
||||
<string name="send_transaction_success">Транзакція успішно підписана і відправлена до блокчейну. Баланс гаманця буде оновлено через деякий час</string>
|
||||
<string name="send_tron_account_activation_error">%1$s — це монета у мережі Tron. Щоб розрахувати комісію та здійснити транзакцію, вам необхідно внести певну кількість Tron (TRX) на свій рахунок.</string>
|
||||
<string name="send_validation_invalid_address">Недійсна адреса</string>
|
||||
<string name="sent_transaction_sent_title">Трансакцію надіслано</string>
|
||||
<string name="settings_card_settings_footer">Підготуйтеся до сканування картку, яку потрібно налаштувати.</string>
|
||||
<string name="settings_card_settings_footer">Підготуйте до сканування картку, яку потрібно налаштувати.</string>
|
||||
<string name="settings_forget_wallet">Забути гаманець</string>
|
||||
<string name="settings_forget_wallet_footer">Це призведе до видалення гаманця з застосунку. Сам гаманець можна додати знову.</string>
|
||||
<string name="settings_wallet_name_title">Ім\'я</string>
|
||||
<string name="staking_active">Активний</string>
|
||||
<string name="staking_active_footer">Щоб вивести активи зі стейкінгу, натисніть тут.</string>
|
||||
<string name="staking_active_footer">Для завершення стейкінгу натисніть на блок вище</string>
|
||||
<string name="staking_amount_requirement_error">Сума для стейкінгу має бути не менше %s</string>
|
||||
<string name="staking_claim_unstaked">Зняти кошти</string>
|
||||
<string name="staking_details_annual_percentage_rate">Процентна ставка</string>
|
||||
<string name="staking_details_annual_percentage_rate_info">Річний відсоток, який ви можете отримати, беручи участь у стейкінгу.</string>
|
||||
<string name="staking_details_apr">APR</string>
|
||||
<string name="staking_details_available">Доступно</string>
|
||||
|
|
@ -703,7 +690,7 @@
|
|||
<string name="staking_reward_schedule_month">Місяць</string>
|
||||
<string name="staking_reward_schedule_week">Тиждень</string>
|
||||
<string name="staking_rewards">Винагороди</string>
|
||||
<string name="staking_stake_locked">Застейкати</string>
|
||||
<string name="staking_stake_locked">Стейкінг закрито</string>
|
||||
<string name="staking_stake_more">Застейкати більше</string>
|
||||
<string name="staking_title_stake">Застейкати %s</string>
|
||||
<string name="staking_title_unstake">Зняти зі стейкінгу %s</string>
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@
|
|||
<string name="common_enable">允許</string>
|
||||
<string name="common_enabled">啟用</string>
|
||||
<string name="common_error">錯誤</string>
|
||||
<string name="common_fee_label">費用</string>
|
||||
<string name="common_import">導入</string>
|
||||
<string name="common_network_fee_title">網路費</string>
|
||||
<string name="common_no">否</string>
|
||||
<string name="common_ok">OK</string>
|
||||
<string name="common_origin_card">主卡片</string>
|
||||
<string name="common_range">%1$s-%2$s</string>
|
||||
<string name="common_reject">拒絕</string>
|
||||
<string name="common_rename">重新命名</string>
|
||||
<string name="common_save_changes">保存設置</string>
|
||||
|
|
@ -250,24 +250,11 @@
|
|||
<string name="scan_card_settings_message">掃描卡片以更改其設置。這些更改只會影響您掃描過的卡,不會影響綁定到您錢包的其他卡。</string>
|
||||
<string name="scan_card_settings_title">準備好您的卡!</string>
|
||||
<string name="send_amount_label">數量</string>
|
||||
<string name="send_destination_hint_address">地址</string>
|
||||
<string name="send_error_address_same_as_wallet">地址與錢包地址相同</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">標籤無效。它不會被添加到交易中</string>
|
||||
<string name="send_extras_error_invalid_memo">Memo無效。 它不會被添加到交易中</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">包含費用</string>
|
||||
<string name="send_fee_picker_low">低</string>
|
||||
<string name="send_fee_picker_normal">正常</string>
|
||||
<string name="send_fee_picker_priority">優先</string>
|
||||
<string name="send_max_amount_label">最大值</string>
|
||||
<string name="send_title_currency_format">發送 %s</string>
|
||||
<string name="send_total_label">總計</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s 和 %2$s 將被發送</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (費用包含: %2$s)</string>
|
||||
<string name="send_total_subtitle_format">%s 將被發送</string>
|
||||
<string name="send_transaction_success">交易已成功簽署並發送至區塊鏈節點。錢包餘額稍後更新</string>
|
||||
<string name="send_validation_invalid_address">無效地址</string>
|
||||
<string name="story_awe_description">安全地存儲您的加密貨幣,同時將私鑰保存在您的卡中</string>
|
||||
<string name="story_awe_title">創新式的硬體錢包</string>
|
||||
<string name="story_backup_description">最多 3張實體卡片 到一個錢包</string>
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
<string name="common_access_denied">Access denied</string>
|
||||
<string name="common_all">All</string>
|
||||
<string name="common_allow">Allow</string>
|
||||
<string name="common_analytics">Analytics</string>
|
||||
<string name="common_apply">Apply</string>
|
||||
<string name="common_approval">Approval</string>
|
||||
<string name="common_approve">Approve</string>
|
||||
|
|
@ -82,6 +83,7 @@
|
|||
<string name="common_buy_currency">Go to %1$s</string>
|
||||
<string name="common_camera_denied_alert_message">You have not given access to your camera, please adjust your privacy settings</string>
|
||||
<string name="common_cancel">Cancel</string>
|
||||
<string name="common_choose_action">Choose action</string>
|
||||
<string name="common_claim_rewards">Claim rewards</string>
|
||||
<string name="common_close">Close</string>
|
||||
<string name="common_continue">Continue</string>
|
||||
|
|
@ -104,7 +106,6 @@
|
|||
<string name="common_explore">Explore</string>
|
||||
<string name="common_explore_transaction_history">Explore transaction history</string>
|
||||
<string name="common_explorer">Explorer</string>
|
||||
<string name="common_fee_label">Fee</string>
|
||||
<string name="common_fee_selector_footer">Network fees are charges users pay to process and confirm transactions. The fee amount can be affected by network congestion, transaction size, and execution priority. %s</string>
|
||||
<string name="common_fee_selector_option_fast">Fast</string>
|
||||
<string name="common_fee_selector_option_market">Market</string>
|
||||
|
|
@ -114,6 +115,10 @@
|
|||
<string name="common_go_to_provider">Go to provider</string>
|
||||
<string name="common_go_to_token">Go to token</string>
|
||||
<string name="common_import">Import</string>
|
||||
<plurals name="common_in_days">
|
||||
<item quantity="one">in %d day</item>
|
||||
<item quantity="other">in %d days</item>
|
||||
</plurals>
|
||||
<string name="common_later">Later</string>
|
||||
<string name="common_locked">Locked</string>
|
||||
<string name="common_main_network">Main network</string>
|
||||
|
|
@ -169,7 +174,9 @@
|
|||
<string name="custom_token_contract_address_input_title">Contract address</string>
|
||||
<string name="custom_token_creation_error_invalid_contract_address">Contract address is invalid</string>
|
||||
<string name="custom_token_creation_error_network_not_selected">Please select the network</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Decimal must be a valid integer, up to %li</string>
|
||||
<string name="custom_token_creation_error_token_already_exist_message">This token has already been added to the list</string>
|
||||
<string name="custom_token_creation_error_token_already_exist_title">Token already exists</string>
|
||||
<string name="custom_token_creation_error_wrong_decimals">Decimal must be a valid integer, up to %d</string>
|
||||
<string name="custom_token_custom_derivation">Custom derivation</string>
|
||||
<string name="custom_token_custom_derivation_placeholder">E. g. m/00\'/0000\'/0\'/0/0</string>
|
||||
<string name="custom_token_custom_derivation_title">Enter custom derivation</string>
|
||||
|
|
@ -344,7 +351,9 @@
|
|||
<string name="markets_common_my_portfolio">My portfolio</string>
|
||||
<string name="markets_common_title">Market</string>
|
||||
<string name="markets_generate_addresses_notification">To generate addresses for selected networks, you must scan your Tangem Wallet card</string>
|
||||
<string name="markets_insights_info_description_message">This section’s data is sourced from the following networks: %s</string>
|
||||
<string name="markets_loading_error_title">Unable to load the data…</string>
|
||||
<string name="markets_loading_no_data_title">No data</string>
|
||||
<string name="markets_quick_actions">Quick actions</string>
|
||||
<string name="markets_search_result_title">Result</string>
|
||||
<string name="markets_search_see_tokens_under_100k">See tokens under 100k market cap</string>
|
||||
|
|
@ -370,7 +379,7 @@
|
|||
<item quantity="one">Based on %d rating</item>
|
||||
<item quantity="other">Based on %d ratings</item>
|
||||
</plurals>
|
||||
<string name="markets_token_details_blockchain_site">Blockchain site</string>
|
||||
<string name="markets_token_details_blockchain_site">Website</string>
|
||||
<string name="markets_token_details_buy_pressure">Buy pressure</string>
|
||||
<string name="markets_token_details_buy_pressure_description">The difference between buyers volume and sellers volume</string>
|
||||
<string name="markets_token_details_circulating_supply">Circulating supply</string>
|
||||
|
|
@ -568,18 +577,11 @@
|
|||
<string name="send_custom_kaspa_per_utxo_footer">The fee required for using each unspent transaction output (UTXO) in the Kaspa network. The more UTXOs you use in a transaction, the higher the fee will be.</string>
|
||||
<string name="send_custom_kaspa_per_utxo_title">KAS per UTXO</string>
|
||||
<string name="send_date_format">%1$s, %2$s</string>
|
||||
<string name="send_destination_hint_address">Address</string>
|
||||
<string name="send_destination_tag_field">Destination Tag</string>
|
||||
<string name="send_enter_address_field">Enter address</string>
|
||||
<string name="send_error_address_same_as_wallet">Address is the same as wallet address</string>
|
||||
<string name="send_extras_error_invalid_destination_tag">Invalid Tag. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_error_invalid_memo">Invalid Memo. It won\'t be added to the transaction.</string>
|
||||
<string name="send_extras_hint_destination_tag">Tag</string>
|
||||
<string name="send_extras_hint_memo">Memo</string>
|
||||
<string name="send_fee_include_description">Include fee</string>
|
||||
<string name="send_fee_picker_low">Low</string>
|
||||
<string name="send_fee_picker_normal">Normal</string>
|
||||
<string name="send_fee_picker_priority">Priority</string>
|
||||
<string name="send_fee_unreachable_error_text">Check your network connection</string>
|
||||
<string name="send_fee_unreachable_error_title">Network fee info unreachable</string>
|
||||
<string name="send_from_wallet_android">From</string>
|
||||
|
|
@ -594,7 +596,7 @@
|
|||
<string name="send_network_fee_warning_title">Network fee coverage</string>
|
||||
<string name="send_notification_exceed_balance_text">Insufficient funds for the transfer, as the total of the fee and transfer amount exceeds the existing balance</string>
|
||||
<string name="send_notification_exceed_balance_title">Total exceeds balance</string>
|
||||
<string name="send_notification_existential_deposit_text">The account will be wiped from the blockchain if a balance goes below the existential deposit. Please leave %s on your balance.</string>
|
||||
<string name="send_notification_existential_deposit_text">A balance of at least %s is required to keep your account on the blockchain to prevent security risks. This amount will remain in your balance and cannot be withdrawn.</string>
|
||||
<string name="send_notification_existential_deposit_title">Existential deposit</string>
|
||||
<string name="send_notification_fee_too_high_text">The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.</string>
|
||||
<string name="send_notification_fee_too_high_title">Custom fee is high</string>
|
||||
|
|
@ -628,21 +630,15 @@
|
|||
<string name="send_summary_title">Send %s</string>
|
||||
<string name="send_summary_transaction_description">You are sending **%1$s** including a network fee of %2$s</string>
|
||||
<string name="send_summary_transaction_description_no_fiat_fee">You are sending **%1$s** and %2$s</string>
|
||||
<string name="send_title_currency_format">Sending %s</string>
|
||||
<string name="send_total_label">Total</string>
|
||||
<string name="send_total_subtitle_asset_format">%1$s and %2$s will be sent</string>
|
||||
<string name="send_total_subtitle_fiat_format">≈ %1$s (inc. fee: %2$s)</string>
|
||||
<string name="send_total_subtitle_format">%s will be sent</string>
|
||||
<string name="send_transaction_success">Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while</string>
|
||||
<string name="send_tron_account_activation_error">%1$s is an asset in the Tron network. To calculate the fee and make a transaction you need to deposit some Tron (TRX) in your account.</string>
|
||||
<string name="send_validation_invalid_address">Invalid address</string>
|
||||
<string name="sent_transaction_sent_title">Transaction sent</string>
|
||||
<string name="settings_card_settings_footer">Prepare to scan card you want to setup.</string>
|
||||
<string name="settings_forget_wallet">Forget wallet</string>
|
||||
<string name="settings_forget_wallet_footer">This will remove the wallet from the application. The wallet itself can be added again.</string>
|
||||
<string name="settings_wallet_name_title">Name</string>
|
||||
<string name="staking_active">Active</string>
|
||||
<string name="staking_active_footer">To unstake your assets, click here.</string>
|
||||
<string name="staking_active_footer">To unstake your assets, tap the block above</string>
|
||||
<string name="staking_amount_requirement_error">The amount to stake must be at least %s</string>
|
||||
<string name="staking_claim_unstaked">Claim unstaked</string>
|
||||
<string name="staking_details_annual_percentage_rate">Annual percentage rate</string>
|
||||
|
|
@ -655,25 +651,28 @@
|
|||
<string name="staking_details_market_rating">Market rating</string>
|
||||
<string name="staking_details_metrics_block_header">Metrics</string>
|
||||
<string name="staking_details_minimum_requirement">Minimum Requirement</string>
|
||||
<string name="staking_details_no_rewards_to_claim">No rewards to claim</string>
|
||||
<string name="staking_details_no_rewards_to_claim">No rewards</string>
|
||||
<string name="staking_details_reward_claiming">Reward claiming</string>
|
||||
<string name="staking_details_reward_claiming_info">Method of receiving staking rewards.\nIt can be either automatic, where the reward is credited to your address, or manual, where you need to withdraw the reward by creating a transaction to receive it.</string>
|
||||
<string name="staking_details_reward_schedule">Reward schedule</string>
|
||||
<string name="staking_details_reward_schedule_info">This is a schedule that determines when participants in staking receive their rewards.</string>
|
||||
<string name="staking_details_rewards_to_claim">Rewards to claim: %s</string>
|
||||
<string name="staking_details_rewards_to_claim">Rewards: %s</string>
|
||||
<string name="staking_details_title">Staking %s</string>
|
||||
<string name="staking_details_unbonding_period">Unbonding Period</string>
|
||||
<string name="staking_details_unbonding_period_info">The period you must wait after requesting to withdraw funds from staking before the tokens become available.</string>
|
||||
<string name="staking_details_warmup_period">Warmup period</string>
|
||||
<string name="staking_details_warmup_period_info">The allocated time for activating participation in staking.</string>
|
||||
<string name="staking_locked">Locked</string>
|
||||
<string name="staking_migrate">Migrate</string>
|
||||
<string name="staking_native">Native staking</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_day">Staking allow you to earn %1$s. Your staking rewards arrive every day.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_hour">Staking allow you to earn %1$s. Your staking rewards arrive every hour.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_month">Staking allow you to earn %1$s. Your staking rewards arrive every month.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_week">Staking allow you to earn %1$s. Your staking rewards arrive every week.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_day">Staking allows you to earn %1$s. Your staking rewards arrive every day.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_hour">Staking allows you to earn %1$s. Your staking rewards arrive every hour.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_month">Staking allows you to earn %1$s. Your staking rewards arrive every month.</string>
|
||||
<string name="staking_notification_earn_rewards_text_period_week">Staking allows you to earn %1$s. Your staking rewards arrive every week.</string>
|
||||
<string name="staking_notification_earn_rewards_title">Earn staking rewards</string>
|
||||
<string name="staking_notification_unstake_text">Rewards stop accruing immediately after you unstake. The unstaking process takes %s.</string>
|
||||
<string name="staking_notification_unstake_text">Rewards stop accruing immediately after you start unstaking. The unstaking process takes %s.</string>
|
||||
<string name="staking_preparing">Preparing</string>
|
||||
<string name="staking_ready_to_withdraw">Ready to withdraw</string>
|
||||
<string name="staking_rebond">Rebond</string>
|
||||
<string name="staking_restake">Restake</string>
|
||||
<string name="staking_restake_rewards">Restake rewards</string>
|
||||
|
|
@ -694,6 +693,7 @@
|
|||
<string name="staking_stake_more">Stake more</string>
|
||||
<string name="staking_title_stake">Stake %s</string>
|
||||
<string name="staking_title_unstake">Unstake %s</string>
|
||||
<string name="staking_unbonding">Unbonding</string>
|
||||
<string name="staking_unlocked_locked">Unlock locked</string>
|
||||
<string name="staking_unstaked">Unstaked</string>
|
||||
<string name="staking_unstaked_footer">Check unstaked to claim your assets</string>
|
||||
|
|
@ -875,8 +875,8 @@
|
|||
<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>
|
||||
<string name="warning_manage_tokens_legacy_derivation_message">Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds.</string>
|
||||
<string name="warning_matic_migration_title">MATIC to POL Migration</string>
|
||||
<string name="warning_matic_migration_message">MATIC is being migrated to POL. However there is no deadline set and MATIC isn\'t being deprecated yet. You can safely continue using MATIC token or use exchanges to swap it for POL.</string>
|
||||
<string name="warning_matic_migration_title">MATIC to POL Migration</string>
|
||||
<plurals name="warning_missing_derivation_message">
|
||||
<item quantity="one">Use your card to get an address for %d network</item>
|
||||
<item quantity="other">Use your card to get an addresses for %d networks</item>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ dependencies {
|
|||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.navigation)
|
||||
implementation(deps.compose.navigation.hilt)
|
||||
implementation(deps.compose.reorderable)
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.compose.accompanist.systemUiController)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package com.tangem.core.ui.coil
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Matrix
|
||||
import coil.size.Size
|
||||
import coil.transform.Transformation
|
||||
|
||||
class RotationTransformation(private val angle: Float) : Transformation {
|
||||
|
||||
override val cacheKey: String = "rotate:$angle"
|
||||
|
||||
override suspend fun transform(input: Bitmap, size: Size): Bitmap {
|
||||
val matrix = Matrix().apply {
|
||||
val centerX = input.width / 2f
|
||||
val centerY = input.height / 2f
|
||||
|
||||
postRotate(angle, centerX, centerY)
|
||||
}
|
||||
|
||||
return Bitmap.createBitmap(input, 0, 0, input.width, input.height, matrix, true)
|
||||
}
|
||||
}
|
||||
|
|
@ -49,10 +49,10 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
@Composable
|
||||
fun BasicDialog(
|
||||
message: String,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
|
|
@ -72,7 +72,7 @@ fun BasicDialog(
|
|||
fun SimpleOkDialog(message: String, onDismissDialog: () -> Unit) {
|
||||
TangemDialog(
|
||||
type = DialogType.Message(message),
|
||||
confirmButton = DialogButton(onClick = onDismissDialog),
|
||||
confirmButton = DialogButtonUM(onClick = onDismissDialog),
|
||||
onDismissDialog = onDismissDialog,
|
||||
title = null,
|
||||
dismissButton = null,
|
||||
|
|
@ -97,12 +97,12 @@ fun SimpleOkDialog(message: String, onDismissDialog: () -> Unit) {
|
|||
@Composable
|
||||
fun TextInputDialog(
|
||||
fieldValue: TextFieldValue,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
onValueChange: (TextFieldValue) -> Unit,
|
||||
textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
|
||||
textFieldParams: AdditionalTextInputDialogUM = remember { AdditionalTextInputDialogUM() },
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
|
|
@ -125,12 +125,12 @@ fun TextInputDialog(
|
|||
@Composable
|
||||
fun TextInputDialog(
|
||||
fieldValue: String,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
onValueChange: (String) -> Unit,
|
||||
textFieldParams: AdditionalTextInputDialogParams = remember { AdditionalTextInputDialogParams() },
|
||||
textFieldParams: AdditionalTextInputDialogUM = remember { AdditionalTextInputDialogUM() },
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
|
|
@ -154,7 +154,7 @@ fun TextInputDialog(
|
|||
fun SelectorDialog(
|
||||
selectedItemIndex: Int,
|
||||
items: ImmutableList<String>,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onSelect: (index: Int) -> Unit,
|
||||
onDismissDialog: () -> Unit,
|
||||
title: String? = null,
|
||||
|
|
@ -180,7 +180,7 @@ fun SelectorDialog(
|
|||
* @param enabled If false button will be disabled
|
||||
* @param onClick Button click callback
|
||||
*/
|
||||
data class DialogButton(
|
||||
data class DialogButtonUM(
|
||||
val title: String? = null,
|
||||
val warning: Boolean = false,
|
||||
val enabled: Boolean = true,
|
||||
|
|
@ -190,7 +190,7 @@ data class DialogButton(
|
|||
/**
|
||||
* Additional params for dialog text field
|
||||
*/
|
||||
data class AdditionalTextInputDialogParams(
|
||||
data class AdditionalTextInputDialogUM(
|
||||
val label: String? = null,
|
||||
val placeholder: String? = null,
|
||||
val caption: String? = null,
|
||||
|
|
@ -203,10 +203,10 @@ data class AdditionalTextInputDialogParams(
|
|||
@Composable
|
||||
private fun TangemDialog(
|
||||
type: DialogType,
|
||||
confirmButton: DialogButton,
|
||||
confirmButton: DialogButtonUM,
|
||||
onDismissDialog: () -> Unit,
|
||||
title: String? = null,
|
||||
dismissButton: DialogButton? = null,
|
||||
dismissButton: DialogButtonUM? = null,
|
||||
properties: DialogProperties = DialogProperties(),
|
||||
) {
|
||||
Dialog(properties = properties, onDismissRequest = onDismissDialog) {
|
||||
|
|
@ -304,7 +304,11 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DialogButtons(confirmButton: DialogButton, dismissButton: DialogButton?, modifier: Modifier = Modifier) {
|
||||
private fun DialogButtons(
|
||||
confirmButton: DialogButtonUM,
|
||||
dismissButton: DialogButtonUM?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
|
|
@ -413,13 +417,13 @@ private sealed class DialogType {
|
|||
data class TextInput(
|
||||
val value: TextFieldValue,
|
||||
val onValueChange: (TextFieldValue) -> Unit,
|
||||
val params: AdditionalTextInputDialogParams = AdditionalTextInputDialogParams(),
|
||||
val params: AdditionalTextInputDialogUM = AdditionalTextInputDialogUM(),
|
||||
) : DialogType()
|
||||
|
||||
data class SimpleTextInput(
|
||||
val value: String,
|
||||
val onValueChange: (String) -> Unit,
|
||||
val params: AdditionalTextInputDialogParams = AdditionalTextInputDialogParams(),
|
||||
val params: AdditionalTextInputDialogUM = AdditionalTextInputDialogUM(),
|
||||
) : DialogType()
|
||||
|
||||
data class Selector(
|
||||
|
|
@ -445,8 +449,8 @@ private fun BasicDialogPreview() {
|
|||
message = "All protected passwords will be deleted from the secure storage, you must enter the wallet " +
|
||||
"password to work with the app",
|
||||
title = "Attention",
|
||||
confirmButton = DialogButton {},
|
||||
dismissButton = DialogButton {},
|
||||
confirmButton = DialogButtonUM {},
|
||||
dismissButton = DialogButtonUM {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -478,8 +482,8 @@ private fun WarningBasicDialogSample(modifier: Modifier = Modifier) {
|
|||
message = "All protected passwords will be deleted from the secure storage, you must enter the wallet " +
|
||||
"password to work with the app",
|
||||
title = "Attention",
|
||||
confirmButton = DialogButton(warning = true) {},
|
||||
dismissButton = DialogButton {},
|
||||
confirmButton = DialogButtonUM(warning = true) {},
|
||||
dismissButton = DialogButtonUM {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -502,10 +506,10 @@ private fun TextInputDialogSample(modifier: Modifier = Modifier) {
|
|||
TextInputDialog(
|
||||
fieldValue = TextFieldValue(text = ""),
|
||||
title = "Rename Wallet",
|
||||
confirmButton = DialogButton {},
|
||||
confirmButton = DialogButtonUM {},
|
||||
onDismissDialog = {},
|
||||
onValueChange = {},
|
||||
textFieldParams = AdditionalTextInputDialogParams(
|
||||
textFieldParams = AdditionalTextInputDialogUM(
|
||||
label = "Wallet name",
|
||||
),
|
||||
)
|
||||
|
|
@ -530,7 +534,7 @@ private fun SelectorDialogPreview(@PreviewParameter(SelctorDialogParamsProvider:
|
|||
title = param.title,
|
||||
items = param.items,
|
||||
selectedItemIndex = param.selectedItemIndex,
|
||||
confirmButton = DialogButton(title = "Cancel", onClick = {}),
|
||||
confirmButton = DialogButtonUM(title = "Cancel", onClick = {}),
|
||||
onSelect = {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -101,7 +102,11 @@ fun TextShimmer(
|
|||
* Height and min width will be set automatically
|
||||
*/
|
||||
@Composable
|
||||
fun SmallButtonShimmer(modifier: Modifier = Modifier, withIcon: Boolean = false) {
|
||||
fun SmallButtonShimmer(
|
||||
modifier: Modifier = Modifier,
|
||||
shape: Shape = RoundedCornerShape(size = TangemTheme.dimens.radius16),
|
||||
withIcon: Boolean = false,
|
||||
) {
|
||||
PrimarySmallButton(
|
||||
config = SmallButtonConfig(
|
||||
text = stringReference("B"),
|
||||
|
|
@ -113,7 +118,7 @@ fun SmallButtonShimmer(modifier: Modifier = Modifier, withIcon: Boolean = false)
|
|||
},
|
||||
),
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius16))
|
||||
.clip(shape)
|
||||
.shimmer(LocalTangemShimmer.current),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import androidx.compose.runtime.Immutable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
|
|
@ -25,28 +27,44 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
@Immutable
|
||||
class InformationBlockContentScope(val scope: BoxScope) : BoxScope by scope
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
@Composable
|
||||
fun InformationBlock(
|
||||
title: @Composable BoxScope.() -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
contentHorizontalPadding: Dp = TangemTheme.dimens.spacing12,
|
||||
shape: Shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
action: (@Composable BoxScope.() -> Unit)? = null,
|
||||
content: (@Composable InformationBlockContentScope.() -> Unit)? = null,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.clip(shape)
|
||||
.background(color = TangemTheme.colors.background.action),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
val minHeight = if (action == null) TangemTheme.dimens.size36 else TangemTheme.dimens.size40
|
||||
val padding = if (action == null) {
|
||||
PaddingValues(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing4,
|
||||
)
|
||||
} else {
|
||||
PaddingValues(
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
top = TangemTheme.dimens.spacing11,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing5,
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size40)
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing12,
|
||||
bottom = TangemTheme.dimens.spacing6,
|
||||
)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12),
|
||||
.heightIn(min = minHeight)
|
||||
.padding(paddingValues = padding),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
|
|
@ -72,7 +90,7 @@ fun InformationBlock(
|
|||
if (content != null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12)
|
||||
.padding(horizontal = contentHorizontalPadding)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
val scope = InformationBlockContentScope(scope = this)
|
||||
|
|
|
|||
|
|
@ -11,4 +11,9 @@ data class TangemBottomSheetConfig(
|
|||
val isShow: Boolean,
|
||||
val onDismissRequest: () -> Unit,
|
||||
val content: TangemBottomSheetConfigContent,
|
||||
)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val Empty = TangemBottomSheetConfig(false, {}, TangemBottomSheetConfigContent.Empty)
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ data class SmallButtonConfig(
|
|||
val text: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
val icon: TangemButtonIconPosition = TangemButtonIconPosition.None,
|
||||
val enabled: Boolean = true,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -57,6 +58,7 @@ fun SecondarySmallButton(config: SmallButtonConfig, modifier: Modifier = Modifie
|
|||
SmallButton(config = config, isPrimary = false, modifier = modifier)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier: Modifier = Modifier) {
|
||||
val shape = RoundedCornerShape(size = TangemTheme.dimens.radius16)
|
||||
|
|
@ -77,7 +79,7 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
color = backgroundColor,
|
||||
shape = shape,
|
||||
)
|
||||
.clickable(enabled = true, onClick = config.onClick)
|
||||
.clickable(enabled = config.enabled, onClick = config.onClick)
|
||||
.padding(
|
||||
paddingValues = when (config.icon) {
|
||||
is TangemButtonIconPosition.None -> PaddingValues(
|
||||
|
|
@ -100,7 +102,11 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
iconPosition = config.icon,
|
||||
text = {
|
||||
val textColor by animateColorAsState(
|
||||
targetValue = if (isPrimary) TangemTheme.colors.text.primary2 else TangemTheme.colors.text.primary1,
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.text.disabled
|
||||
isPrimary -> TangemTheme.colors.text.primary2
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
label = "Update text color",
|
||||
)
|
||||
|
||||
|
|
@ -116,7 +122,11 @@ private fun SmallButton(config: SmallButtonConfig, isPrimary: Boolean, modifier:
|
|||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size16),
|
||||
painter = painterResource(id = iconResId),
|
||||
tint = TangemTheme.colors.icon.secondary,
|
||||
tint = if (config.enabled) {
|
||||
TangemTheme.colors.icon.secondary
|
||||
} else {
|
||||
TangemTheme.colors.icon.inactive
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
|
|
@ -174,5 +184,12 @@ private fun ButtonsSample() {
|
|||
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
|
||||
),
|
||||
)
|
||||
SecondarySmallButton(
|
||||
config = config.copy(
|
||||
text = TextReference.Str(value = "Add token"),
|
||||
icon = TangemButtonIconPosition.Start(iconResId = R.drawable.ic_plus_24),
|
||||
enabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
package com.tangem.core.ui.components.buttons.common
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -36,6 +41,7 @@ fun TangemButton(
|
|||
textStyle: TextStyle = TangemTheme.typography.button,
|
||||
shape: Shape = size.toShape(),
|
||||
iconPadding: Dp = size.toIconPadding(),
|
||||
animateContentChange: Boolean = false,
|
||||
) {
|
||||
val multipleClickPreventer = remember { MultipleClickPreventer.get() }
|
||||
|
||||
|
|
@ -56,6 +62,7 @@ fun TangemButton(
|
|||
buttonIcon = icon,
|
||||
iconPadding = iconPadding,
|
||||
showProgress = showProgress,
|
||||
animateContentChange = animateContentChange,
|
||||
progressIndicator = {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.buttonContentSize(maxContentSize),
|
||||
|
|
@ -108,24 +115,54 @@ private inline fun RowScope.ButtonContentContainer(
|
|||
buttonIcon: TangemButtonIconPosition,
|
||||
iconPadding: Dp,
|
||||
showProgress: Boolean,
|
||||
animateContentChange: Boolean,
|
||||
progressIndicator: @Composable RowScope.() -> Unit,
|
||||
text: @Composable RowScope.() -> Unit,
|
||||
icon: @Composable RowScope.(Int) -> Unit,
|
||||
crossinline text: @Composable RowScope.() -> Unit,
|
||||
crossinline icon: @Composable RowScope.(Int) -> Unit,
|
||||
additionalText: @Composable () -> Unit,
|
||||
) {
|
||||
if (showProgress) {
|
||||
progressIndicator()
|
||||
} else {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Row(horizontalArrangement = Arrangement.Center) {
|
||||
if (buttonIcon is TangemButtonIconPosition.Start) {
|
||||
icon(buttonIcon.iconResId)
|
||||
Spacer(modifier = Modifier.requiredWidth(iconPadding))
|
||||
if (animateContentChange) {
|
||||
AnimatedContent(
|
||||
targetState = buttonIcon,
|
||||
transitionSpec = {
|
||||
fadeIn(tween(durationMillis = 220)) togetherWith
|
||||
fadeOut(tween(durationMillis = 220))
|
||||
},
|
||||
label = "button text with icon",
|
||||
) { iconState ->
|
||||
Row(horizontalArrangement = Arrangement.Center) {
|
||||
when (iconState) {
|
||||
is TangemButtonIconPosition.Start -> {
|
||||
icon(iconState.iconResId)
|
||||
Spacer(modifier = Modifier.requiredWidth(iconPadding))
|
||||
text()
|
||||
}
|
||||
is TangemButtonIconPosition.End -> {
|
||||
text()
|
||||
Spacer(modifier = Modifier.requiredWidth(iconPadding))
|
||||
icon(iconState.iconResId)
|
||||
}
|
||||
is TangemButtonIconPosition.None -> {
|
||||
text()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
text()
|
||||
if (buttonIcon is TangemButtonIconPosition.End) {
|
||||
Spacer(modifier = Modifier.requiredWidth(iconPadding))
|
||||
icon(buttonIcon.iconResId)
|
||||
} else {
|
||||
Row(horizontalArrangement = Arrangement.Center) {
|
||||
if (buttonIcon is TangemButtonIconPosition.Start) {
|
||||
icon(buttonIcon.iconResId)
|
||||
Spacer(modifier = Modifier.requiredWidth(iconPadding))
|
||||
}
|
||||
text()
|
||||
if (buttonIcon is TangemButtonIconPosition.End) {
|
||||
Spacer(modifier = Modifier.requiredWidth(iconPadding))
|
||||
icon(buttonIcon.iconResId)
|
||||
}
|
||||
}
|
||||
}
|
||||
additionalText()
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ sealed class CurrencyIconState {
|
|||
* Represents a token icon.
|
||||
*
|
||||
* @property url The URL where the token icon can be fetched from. May be `null` if not found.
|
||||
* @property topBadgeIconResId The drawable resource ID for the network badge.
|
||||
* @property topBadgeIconResId The drawable resource ID for the network badge. May be `null`.
|
||||
* @property isGrayscale Specifies whether to show the icon in grayscale.
|
||||
* @property showCustomBadge Specifies whether to show the custom token badge.
|
||||
* @property fallbackTint The color to be used for tinting the fallback icon.
|
||||
|
|
@ -46,7 +46,7 @@ sealed class CurrencyIconState {
|
|||
*/
|
||||
data class TokenIcon(
|
||||
val url: String?,
|
||||
@DrawableRes override val topBadgeIconResId: Int,
|
||||
@DrawableRes override val topBadgeIconResId: Int?,
|
||||
override val isGrayscale: Boolean,
|
||||
override val showCustomBadge: Boolean,
|
||||
val fallbackTint: Color,
|
||||
|
|
@ -81,4 +81,28 @@ sealed class CurrencyIconState {
|
|||
override val showCustomBadge: Boolean = false
|
||||
override val topBadgeIconResId: Int? = null
|
||||
}
|
||||
|
||||
fun copySealed(
|
||||
isGrayscale: Boolean = this.isGrayscale,
|
||||
showCustomBadge: Boolean = this.showCustomBadge,
|
||||
topBadgeIconResId: Int? = this.topBadgeIconResId,
|
||||
): CurrencyIconState = when (this) {
|
||||
is CoinIcon -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
showCustomBadge = showCustomBadge,
|
||||
)
|
||||
is CustomTokenIcon -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
showCustomBadge = showCustomBadge,
|
||||
topBadgeIconResId = topBadgeIconResId ?: this.topBadgeIconResId,
|
||||
)
|
||||
is TokenIcon -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
showCustomBadge = showCustomBadge,
|
||||
topBadgeIconResId = topBadgeIconResId,
|
||||
)
|
||||
is Loading,
|
||||
is Locked,
|
||||
-> this
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusManager
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.platform.SoftwareKeyboardController
|
||||
|
|
@ -69,6 +70,7 @@ fun SearchBar(state: SearchBarUM, modifier: Modifier = Modifier, colors: TextFie
|
|||
textStyle = TangemTheme.typography.body2.copy(
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
cursorBrush = SolidColor(TangemTheme.colors.icon.primary1),
|
||||
decorationBox = @Composable { innerTextField ->
|
||||
DecorationBox(
|
||||
state = state,
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
*/
|
||||
@Composable
|
||||
fun InputRowDefault(
|
||||
title: TextReference,
|
||||
text: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
title: TextReference? = null,
|
||||
titleColor: Color = TangemTheme.colors.text.secondary,
|
||||
textColor: Color = TangemTheme.colors.text.primary1,
|
||||
iconRes: Int? = null,
|
||||
|
|
@ -66,16 +66,18 @@ fun InputRowDefault(
|
|||
modifier = Modifier
|
||||
.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = titleColor,
|
||||
)
|
||||
title?.let {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = titleColor,
|
||||
modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
iconRes?.let {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.core.ui.components.list
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.*
|
||||
|
||||
@Composable
|
||||
fun InfiniteListHandler(listState: LazyListState, onLoadMore: () -> Boolean, buffer: Int = 2) {
|
||||
val loadMore by remember {
|
||||
derivedStateOf {
|
||||
val layoutInfo = listState.layoutInfo
|
||||
val totalItemsNumber = layoutInfo.totalItemsCount
|
||||
val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1
|
||||
|
||||
lastVisibleItemIndex > totalItemsNumber - buffer
|
||||
}
|
||||
}
|
||||
|
||||
val totalItemsCount by remember { derivedStateOf { listState.layoutInfo.totalItemsCount } }
|
||||
var emitted by remember(totalItemsCount) { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(loadMore) {
|
||||
if (loadMore && !emitted) {
|
||||
emitted = onLoadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,21 @@
|
|||
package com.tangem.core.ui.components.marketprice
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/** Price changing type */
|
||||
enum class PriceChangeType {
|
||||
UP, DOWN, NEUTRAL,
|
||||
;
|
||||
|
||||
companion object {
|
||||
@Suppress("MagicNumber")
|
||||
fun fromBigDecimal(priceChangePercent: BigDecimal): PriceChangeType {
|
||||
return when {
|
||||
priceChangePercent < BigDecimal.ZERO -> DOWN
|
||||
priceChangePercent.setScale(4, RoundingMode.HALF_UP) > BigDecimal.ZERO -> UP
|
||||
else -> NEUTRAL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ import androidx.compose.ui.graphics.drawscope.Stroke
|
|||
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.*
|
||||
|
|
@ -68,6 +69,7 @@ private class ChildArrowScope(
|
|||
|
||||
@Composable
|
||||
fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
|
||||
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||
val figureWidth = TangemTheme.dimens.size40
|
||||
|
||||
val strokeColor = TangemTheme.colors.stroke.secondary
|
||||
|
|
@ -86,18 +88,31 @@ fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
|
|||
)
|
||||
val arrowHeadRectDp = DpRect(
|
||||
origin = DpOffset(
|
||||
x = figureWidth - arrowHeadSize.width,
|
||||
x = if (isLtr) {
|
||||
figureWidth - arrowHeadSize.width
|
||||
} else {
|
||||
0.dp
|
||||
},
|
||||
y = figureRectDp.size.center.y - arrowHeadSize.center.y,
|
||||
),
|
||||
size = arrowHeadSize,
|
||||
)
|
||||
|
||||
val curvedArrowRectDp = DpRect(
|
||||
top = figureRectDp.top,
|
||||
left = TangemTheme.dimens.size18,
|
||||
right = figureRectDp.right - arrowHeadRectDp.width,
|
||||
bottom = figureRectDp.size.center.y,
|
||||
)
|
||||
val curvedArrowRectDp = if (isLtr) {
|
||||
DpRect(
|
||||
top = figureRectDp.top,
|
||||
left = TangemTheme.dimens.size18,
|
||||
right = figureRectDp.right - arrowHeadRectDp.width,
|
||||
bottom = figureRectDp.size.center.y,
|
||||
)
|
||||
} else {
|
||||
DpRect(
|
||||
top = figureRectDp.top,
|
||||
left = arrowHeadRectDp.width,
|
||||
right = TangemTheme.dimens.size18 + arrowHeadRectDp.width,
|
||||
bottom = figureRectDp.size.center.y,
|
||||
)
|
||||
}
|
||||
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
|
|
@ -114,20 +129,26 @@ fun ChildArrow(childHeight: Dp, isLastChild: Boolean) {
|
|||
drawScope = this,
|
||||
)
|
||||
|
||||
scope.drawCurveArrow()
|
||||
scope.drawArrowHead()
|
||||
scope.drawCurveArrow(isLtr)
|
||||
scope.drawArrowHead(isLtr)
|
||||
|
||||
if (!isLastChild) {
|
||||
scope.drawArrowLine()
|
||||
scope.drawArrowLine(isLtr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ChildArrowScope.drawArrowHead() {
|
||||
private fun ChildArrowScope.drawArrowHead(isLtr: Boolean) {
|
||||
val arrowHeadPath = Path().apply {
|
||||
moveTo(arrowHeadRect.centerRight)
|
||||
lineTo(arrowHeadRect.topLeft)
|
||||
lineTo(arrowHeadRect.bottomLeft)
|
||||
if (isLtr) {
|
||||
moveTo(arrowHeadRect.centerRight)
|
||||
lineTo(arrowHeadRect.topLeft)
|
||||
lineTo(arrowHeadRect.bottomLeft)
|
||||
} else {
|
||||
moveTo(arrowHeadRect.centerLeft)
|
||||
lineTo(arrowHeadRect.topRight)
|
||||
lineTo(arrowHeadRect.bottomRight)
|
||||
}
|
||||
close()
|
||||
}
|
||||
val paint = Paint().apply {
|
||||
|
|
@ -143,13 +164,21 @@ private fun ChildArrowScope.drawArrowHead() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun ChildArrowScope.drawCurveArrow() {
|
||||
private fun ChildArrowScope.drawCurveArrow(isLtr: Boolean) {
|
||||
val curveArrowPath = Path().apply {
|
||||
moveTo(curvedArrowRect.topLeft)
|
||||
quadraticBezierTo(
|
||||
control = curvedArrowRect.bottomLeft,
|
||||
end = curvedArrowRect.bottomRight,
|
||||
)
|
||||
if (isLtr) {
|
||||
moveTo(curvedArrowRect.topLeft)
|
||||
quadraticBezierTo(
|
||||
control = curvedArrowRect.bottomLeft,
|
||||
end = curvedArrowRect.bottomRight,
|
||||
)
|
||||
} else {
|
||||
moveTo(curvedArrowRect.topRight)
|
||||
quadraticBezierTo(
|
||||
control = curvedArrowRect.bottomRight,
|
||||
end = curvedArrowRect.bottomLeft,
|
||||
)
|
||||
}
|
||||
}
|
||||
drawPath(
|
||||
path = curveArrowPath,
|
||||
|
|
@ -158,11 +187,20 @@ private fun ChildArrowScope.drawCurveArrow() {
|
|||
)
|
||||
}
|
||||
|
||||
private fun ChildArrowScope.drawArrowLine() {
|
||||
drawLine(
|
||||
color = strokeColor,
|
||||
start = curvedArrowRect.topLeft,
|
||||
end = Offset(curvedArrowRect.left, figureRect.bottom),
|
||||
strokeWidth = arrowStrokeWidth,
|
||||
)
|
||||
private fun ChildArrowScope.drawArrowLine(isLtr: Boolean) {
|
||||
if (isLtr) {
|
||||
drawLine(
|
||||
color = strokeColor,
|
||||
start = curvedArrowRect.topLeft,
|
||||
end = Offset(curvedArrowRect.left, figureRect.bottom),
|
||||
strokeWidth = arrowStrokeWidth,
|
||||
)
|
||||
} else {
|
||||
drawLine(
|
||||
color = strokeColor,
|
||||
start = curvedArrowRect.topRight,
|
||||
end = Offset(curvedArrowRect.right, figureRect.bottom),
|
||||
strokeWidth = arrowStrokeWidth,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,8 +29,9 @@ fun BlockchainRow(model: BlockchainRowUM, action: @Composable BoxScope.() -> Uni
|
|||
modifier = modifier
|
||||
.heightIn(min = TangemTheme.dimens.size52)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing8,
|
||||
horizontal = TangemTheme.dimens.spacing8,
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing8,
|
||||
start = TangemTheme.dimens.spacing8,
|
||||
),
|
||||
icon = {
|
||||
RowIcon(
|
||||
|
|
@ -125,7 +126,7 @@ private fun Preview_BlockchainRow(@PreviewParameter(BlockchainRowParameterProvid
|
|||
BlockchainRow(
|
||||
model = state,
|
||||
action = {
|
||||
TangemSwitch(onCheckedChange = { /* [REDACTED_TODO_COMMENT]*/ }, checked = true)
|
||||
TangemSwitch(onCheckedChange = { }, checked = true)
|
||||
},
|
||||
)
|
||||
},
|
||||
|
|
@ -136,6 +137,7 @@ private fun Preview_BlockchainRow(@PreviewParameter(BlockchainRowParameterProvid
|
|||
private class BlockchainRowParameterProvider : CollectionPreviewParameterProvider<BlockchainRowUM>(
|
||||
collection = listOf(
|
||||
BlockchainRowUM(
|
||||
id = "0",
|
||||
name = "BNB BEACON CHAIN",
|
||||
type = "BEP20",
|
||||
iconResId = R.drawable.img_bsc_22,
|
||||
|
|
@ -143,6 +145,7 @@ private class BlockchainRowParameterProvider : CollectionPreviewParameterProvide
|
|||
isSelected = true,
|
||||
),
|
||||
BlockchainRowUM(
|
||||
id = "1",
|
||||
name = "1234567890111213141516171819",
|
||||
type = "BEP20",
|
||||
iconResId = R.drawable.ic_bsc_16,
|
||||
|
|
@ -150,6 +153,7 @@ private class BlockchainRowParameterProvider : CollectionPreviewParameterProvide
|
|||
isSelected = false,
|
||||
),
|
||||
BlockchainRowUM(
|
||||
id = "2",
|
||||
name = "BNB BEACON CHAIN",
|
||||
type = "1234567890111213141516171819",
|
||||
iconResId = R.drawable.ic_bsc_16,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
|
|||
|
||||
@Immutable
|
||||
data class BlockchainRowUM(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val type: String,
|
||||
val iconResId: Int,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component
|
||||
package com.tangem.core.ui.components.token
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -16,15 +16,18 @@ 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.Constraints
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.components.token.internal.*
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.common.component.token.*
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import java.util.UUID
|
||||
import kotlin.math.max
|
||||
|
||||
private const val TITLE_MIN_WIDTH_COEFFICIENT = 0.3
|
||||
|
|
@ -34,12 +37,51 @@ private enum class LayoutId {
|
|||
ICON, TITLE, FIAT_AMOUNT, CRYPTO_AMOUNT, CRYPTO_PRICE, NON_FIAT_CONTENT
|
||||
}
|
||||
|
||||
/**
|
||||
* Token item for non reorderable list
|
||||
*
|
||||
* @param state token item state
|
||||
* @param isBalanceHidden flag that shows/hides balance
|
||||
* @param modifier modifier
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1051-866&t=ew8mbGp2lacuJfFm-4"
|
||||
* >Figma Component</a>
|
||||
*/
|
||||
@Composable
|
||||
internal fun TokenItem(
|
||||
fun TokenItem(
|
||||
state: TokenItemState,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
reorderableTokenListState: ReorderableLazyListState? = null,
|
||||
itemPaddingValues: PaddingValues = PaddingValues(horizontal = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
TokenItem(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier,
|
||||
reorderableTokenListState = null,
|
||||
itemPaddingValues = itemPaddingValues,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Token item for reorderable list
|
||||
*
|
||||
* @param state token item state
|
||||
* @param isBalanceHidden flag that shows/hides balance
|
||||
* @param reorderableTokenListState reorderable token list state
|
||||
* @param modifier modifier
|
||||
* @param itemPaddingValues padding values
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1051-866&t=ew8mbGp2lacuJfFm-4"
|
||||
* >Figma Component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun TokenItem(
|
||||
state: TokenItemState,
|
||||
isBalanceHidden: Boolean,
|
||||
reorderableTokenListState: ReorderableLazyListState?,
|
||||
modifier: Modifier = Modifier,
|
||||
itemPaddingValues: PaddingValues = PaddingValues(horizontal = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
val betweenRowsMargin = TangemTheme.dimens.spacing2
|
||||
|
||||
|
|
@ -47,7 +89,7 @@ internal fun TokenItem(
|
|||
state = state,
|
||||
modifier = modifier
|
||||
.tokenClickable(state = state)
|
||||
.background(color = TangemTheme.colors.background.primary),
|
||||
.padding(itemPaddingValues),
|
||||
) {
|
||||
CurrencyIcon(
|
||||
state = state.iconState,
|
||||
|
|
@ -73,7 +115,7 @@ internal fun TokenItem(
|
|||
)
|
||||
|
||||
TokenPrice(
|
||||
state = state.cryptoPriceState,
|
||||
state = state.subtitleState,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.CRYPTO_PRICE)
|
||||
.padding(end = TangemTheme.dimens.spacing8),
|
||||
|
|
@ -96,17 +138,22 @@ internal fun TokenItem(
|
|||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun Modifier.tokenClickable(state: TokenItemState): Modifier = composed {
|
||||
when (state) {
|
||||
is TokenItemState.Content -> {
|
||||
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
|
||||
combinedClickable(onClick = state.onItemClick, onLongClick = onLongClick)
|
||||
}
|
||||
is TokenItemState.Unreachable -> {
|
||||
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
|
||||
combinedClickable(onClick = state.onItemClick, onLongClick = onLongClick)
|
||||
}
|
||||
is TokenItemState.NoAddress -> {
|
||||
val onLongClick = rememberHapticFeedback(state = state, onAction = state.onItemLongClick)
|
||||
combinedClickable(onClick = {}, onLongClick = onLongClick)
|
||||
is TokenItemState.Content,
|
||||
is TokenItemState.NoAddress,
|
||||
is TokenItemState.Unreachable,
|
||||
-> {
|
||||
val onClick = state.onItemClick
|
||||
val onLongClick = state.onItemLongClick?.let { rememberHapticFeedback(state = state, onAction = it) }
|
||||
|
||||
when {
|
||||
onClick == null && onLongClick == null -> this
|
||||
onClick == null && onLongClick != null -> combinedClickable(onClick = {}, onLongClick = onLongClick)
|
||||
onClick != null && onLongClick == null -> combinedClickable(onClick = onClick)
|
||||
onClick != null && onLongClick != null -> {
|
||||
combinedClickable(onClick = onClick, onLongClick = onLongClick)
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
is TokenItemState.Draggable,
|
||||
is TokenItemState.Loading,
|
||||
|
|
@ -127,10 +174,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
Layout(content = content, modifier = modifier) { measurables, constraints ->
|
||||
|
||||
val layoutWidth = constraints.maxWidth
|
||||
val horizontalPadding = with(density) { dimens.size12.roundToPx() }
|
||||
val verticalPadding = with(density) { dimens.size15.roundToPx() }
|
||||
val layoutWidthWithoutPaddings = layoutWidth - 2 * horizontalPadding
|
||||
|
||||
val titleMinWidth = (layoutWidth * TITLE_MIN_WIDTH_COEFFICIENT).toInt()
|
||||
val priceChangeMinWidth = (layoutWidth * PRICE_MIN_WIDTH_COEFFICIENT).toInt()
|
||||
|
||||
|
|
@ -166,32 +210,36 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
-> {
|
||||
fiatAmount = measurables.measureFiatAmount(
|
||||
state = state,
|
||||
maxWidth = layoutWidthWithoutPaddings - icon.width - titleMinWidth,
|
||||
maxWidth = layoutWidth - icon.width - titleMinWidth,
|
||||
defaultConstraints = constraints,
|
||||
)
|
||||
|
||||
cryptoAmount = measurables.measureCryptoAmount(
|
||||
state = state,
|
||||
maxWidth = layoutWidthWithoutPaddings - icon.width - priceChangeMinWidth,
|
||||
maxWidth = layoutWidth - icon.width - priceChangeMinWidth,
|
||||
defaultConstraints = constraints,
|
||||
)
|
||||
|
||||
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - fiatAmount.width
|
||||
secondRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - cryptoAmount.width
|
||||
firstRowRemainingFreeSpace = layoutWidth - icon.width - fiatAmount.width
|
||||
secondRowRemainingFreeSpace = layoutWidth - icon.width - cryptoAmount.width
|
||||
}
|
||||
is TokenItemState.Draggable -> {
|
||||
cryptoAmount = measurables.measureCryptoAmount(
|
||||
state = state,
|
||||
maxWidth = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width,
|
||||
maxWidth = layoutWidth - icon.width - nonFiatContent.width,
|
||||
defaultConstraints = constraints,
|
||||
)
|
||||
|
||||
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width
|
||||
firstRowRemainingFreeSpace = layoutWidth - icon.width - nonFiatContent.width
|
||||
}
|
||||
is TokenItemState.NoAddress,
|
||||
is TokenItemState.Unreachable,
|
||||
-> {
|
||||
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width
|
||||
firstRowRemainingFreeSpace = layoutWidth - icon.width - nonFiatContent.width
|
||||
|
||||
if (state.subtitleState != null) {
|
||||
secondRowRemainingFreeSpace = layoutWidth - icon.width - nonFiatContent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -222,35 +270,41 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
)
|
||||
|
||||
layout(width = constraints.maxWidth, height = layoutHeight) {
|
||||
icon.placeRelative(x = horizontalPadding, y = (layoutHeight - icon.height).div(other = 2))
|
||||
icon.placeRelative(x = 0, y = (layoutHeight - icon.height).div(other = 2))
|
||||
|
||||
title.placeRelative(
|
||||
x = horizontalPadding + icon.width,
|
||||
x = icon.width,
|
||||
y = when (state) {
|
||||
is TokenItemState.NoAddress,
|
||||
is TokenItemState.Unreachable,
|
||||
-> (layoutHeight - title.height).div(other = 2)
|
||||
-> {
|
||||
if (state.subtitleState == null) {
|
||||
(layoutHeight - title.height).div(other = 2)
|
||||
} else {
|
||||
verticalPadding
|
||||
}
|
||||
}
|
||||
else -> verticalPadding
|
||||
},
|
||||
)
|
||||
|
||||
fiatAmount?.placeRelative(x = layoutWidth - fiatAmount.width - horizontalPadding, y = verticalPadding)
|
||||
fiatAmount?.placeRelative(x = layoutWidth - fiatAmount.width, y = verticalPadding)
|
||||
|
||||
priceChange?.placeRelative(
|
||||
x = horizontalPadding + icon.width,
|
||||
x = icon.width,
|
||||
y = layoutHeight - priceChange.height - verticalPadding,
|
||||
)
|
||||
|
||||
cryptoAmount?.placeRelative(
|
||||
x = when (state) {
|
||||
is TokenItemState.Draggable -> horizontalPadding + icon.width
|
||||
else -> layoutWidth - cryptoAmount.width - horizontalPadding
|
||||
is TokenItemState.Draggable -> icon.width
|
||||
else -> layoutWidth - cryptoAmount.width
|
||||
},
|
||||
y = layoutHeight - cryptoAmount.height - verticalPadding,
|
||||
)
|
||||
|
||||
nonFiatContent.placeRelative(
|
||||
x = layoutWidth - nonFiatContent.width - horizontalPadding,
|
||||
x = layoutWidth - nonFiatContent.width,
|
||||
y = (layoutHeight - nonFiatContent.height).div(other = 2),
|
||||
)
|
||||
}
|
||||
|
|
@ -397,8 +451,8 @@ private fun Preview_TokenItem_InLight(@PreviewParameter(TokenItemStateProvider::
|
|||
|
||||
private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenItemState>(
|
||||
collection = listOf(
|
||||
WalletPreviewData.tokenItemVisibleState.copy(
|
||||
iconState = WalletPreviewData.coinIconState.copy(showCustomBadge = true),
|
||||
tokenItemVisibleState.copy(
|
||||
iconState = coinIconState.copy(showCustomBadge = true),
|
||||
titleState = TokenItemState.TitleState.Content(
|
||||
text = "PolygonPolygonPolygonPolygonPolygonPolygon",
|
||||
hasPending = true,
|
||||
|
|
@ -408,19 +462,122 @@ private class TokenItemStateProvider : CollectionPreviewParameterProvider<TokenI
|
|||
hasStaked = true,
|
||||
),
|
||||
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "5,4123123213123123123123123123 MATIC"),
|
||||
cryptoPriceState = TokenItemState.CryptoPriceState.Content(
|
||||
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
|
||||
price = "312 USD",
|
||||
priceChangePercent = "42.0%",
|
||||
type = PriceChangeType.UP,
|
||||
),
|
||||
),
|
||||
WalletPreviewData.tokenItemUnreachableState,
|
||||
WalletPreviewData.tokenItemNoAddressState,
|
||||
WalletPreviewData.tokenItemDragState,
|
||||
WalletPreviewData.tokenItemHiddenState,
|
||||
WalletPreviewData.loadingTokenItemState,
|
||||
WalletPreviewData.testnetTokenItemVisibleState,
|
||||
WalletPreviewData.customTokenItemVisibleState,
|
||||
WalletPreviewData.customTestnetTokenItemVisibleState,
|
||||
TokenItemState.Unreachable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.Unreachable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent("Token"),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.NoAddress(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.NoAddress(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent("Token"),
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.Draggable(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "3 172,14 $"),
|
||||
),
|
||||
TokenItemState.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = tokenIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = false),
|
||||
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "5,412 MATIC"),
|
||||
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
|
||||
price = "312 USD",
|
||||
priceChangePercent = "2.0%",
|
||||
type = PriceChangeType.UP,
|
||||
),
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
),
|
||||
TokenItemState.Loading(
|
||||
id = "Loading#1",
|
||||
iconState = customTokenIconState.copy(isGrayscale = true),
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
),
|
||||
tokenItemVisibleState.copy(
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon testnet"),
|
||||
iconState = tokenIconState.copy(isGrayscale = true),
|
||||
),
|
||||
tokenItemVisibleState.copy(
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
iconState = customTokenIconState.copy(
|
||||
tint = TangemColorPalette.White,
|
||||
background = TangemColorPalette.Black,
|
||||
),
|
||||
),
|
||||
tokenItemVisibleState.copy(
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon"),
|
||||
iconState = customTokenIconState.copy(isGrayscale = true),
|
||||
),
|
||||
),
|
||||
)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
val coinIconState
|
||||
get() = CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.img_polygon_22,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
)
|
||||
|
||||
val tokenIconState
|
||||
get() = CurrencyIconState.TokenIcon(
|
||||
url = null,
|
||||
topBadgeIconResId = R.drawable.img_polygon_22,
|
||||
fallbackTint = TangemColorPalette.Black,
|
||||
fallbackBackground = TangemColorPalette.Meadow,
|
||||
isGrayscale = false,
|
||||
showCustomBadge = false,
|
||||
)
|
||||
|
||||
private val customTokenIconState
|
||||
get() = CurrencyIconState.CustomTokenIcon(
|
||||
tint = TangemColorPalette.Black,
|
||||
background = TangemColorPalette.Meadow,
|
||||
topBadgeIconResId = R.drawable.img_polygon_22,
|
||||
isGrayscale = false,
|
||||
)
|
||||
|
||||
val tokenItemVisibleState by lazy {
|
||||
TokenItemState.Content(
|
||||
id = UUID.randomUUID().toString(),
|
||||
iconState = coinIconState,
|
||||
titleState = TokenItemState.TitleState.Content(text = "Polygon", hasPending = true),
|
||||
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $", hasStaked = true),
|
||||
cryptoAmountState = TokenItemState.CryptoAmountState.Content(text = "5,412 MATIC"),
|
||||
subtitleState = TokenItemState.SubtitleState.Unknown,
|
||||
onItemClick = {},
|
||||
onItemLongClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
package com.tangem.core.ui.components.token.internal
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
package com.tangem.core.ui.components.token.internal
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
|
|
@ -12,9 +12,9 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorder
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
package com.tangem.core.ui.components.token.internal
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -8,11 +8,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.CryptoAmountState as TokenCryptoAmountState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.CryptoAmountState as TokenCryptoAmountState
|
||||
|
||||
@Composable
|
||||
internal fun TokenCryptoAmount(
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
package com.tangem.core.ui.components.token.internal
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -14,11 +14,11 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.FiatAmountState as TokenFiatAmountState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState as TokenFiatAmountState
|
||||
|
||||
@Composable
|
||||
internal fun TokenFiatAmount(state: TokenFiatAmountState?, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
package com.tangem.core.ui.components.token.internal
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -12,19 +13,23 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.res.painterResource
|
||||
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 com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SpacerW4
|
||||
import com.tangem.core.ui.components.SpacerW6
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.CryptoPriceState as TokenPriceChangeState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.SubtitleState as TokenPriceState
|
||||
|
||||
@Composable
|
||||
internal fun TokenPrice(state: TokenPriceChangeState?, modifier: Modifier = Modifier) {
|
||||
internal fun TokenPrice(state: TokenPriceState?, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TokenPriceChangeState.Content -> {
|
||||
is TokenPriceState.CryptoPriceContent -> {
|
||||
PriceBlock(
|
||||
modifier = modifier,
|
||||
price = state.price,
|
||||
|
|
@ -32,13 +37,12 @@ internal fun TokenPrice(state: TokenPriceChangeState?, modifier: Modifier = Modi
|
|||
priceChangePercent = state.priceChangePercent,
|
||||
)
|
||||
}
|
||||
is TokenPriceChangeState.Unknown -> {
|
||||
PriceText(text = DASH_SIGN, modifier = modifier)
|
||||
}
|
||||
is TokenPriceChangeState.Loading -> {
|
||||
is TokenPriceState.TextContent -> PriceText(text = state.value, modifier = modifier)
|
||||
is TokenPriceState.Unknown -> PriceText(text = DASH_SIGN, modifier = modifier)
|
||||
is TokenPriceState.Loading -> {
|
||||
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
}
|
||||
is TokenPriceChangeState.Locked -> {
|
||||
is TokenPriceState.Locked -> {
|
||||
LockedRectangle(modifier = modifier.placeholderSize())
|
||||
}
|
||||
null -> Unit
|
||||
|
|
@ -122,4 +126,37 @@ private fun Modifier.placeholderSize(): Modifier = composed {
|
|||
return@composed this
|
||||
.padding(vertical = TangemTheme.dimens.spacing2)
|
||||
.size(width = TangemTheme.dimens.size52, height = TangemTheme.dimens.size12)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview(@PreviewParameter(TokenPriceChangeStateProvider::class) state: TokenPriceState) {
|
||||
TangemThemePreview {
|
||||
TokenPrice(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
private class TokenPriceChangeStateProvider : CollectionPreviewParameterProvider<TokenPriceState>(
|
||||
collection = listOf(
|
||||
TokenPriceState.CryptoPriceContent(
|
||||
price = "1.234",
|
||||
priceChangePercent = "2.5%",
|
||||
type = PriceChangeType.UP,
|
||||
),
|
||||
TokenPriceState.CryptoPriceContent(
|
||||
price = "1.234",
|
||||
priceChangePercent = "2.5%",
|
||||
type = PriceChangeType.DOWN,
|
||||
),
|
||||
TokenPriceState.CryptoPriceContent(
|
||||
price = "1.234",
|
||||
priceChangePercent = "2.5%",
|
||||
type = PriceChangeType.NEUTRAL,
|
||||
),
|
||||
TokenPriceState.TextContent(value = "Subtitle"),
|
||||
TokenPriceState.Unknown,
|
||||
TokenPriceState.Loading,
|
||||
TokenPriceState.Locked,
|
||||
),
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.wallet.presentation.common.component.token
|
||||
package com.tangem.core.ui.components.token.internal
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
|
|
@ -13,10 +13,10 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.wallet.impl.R
|
||||
import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TitleState as TokenTitleState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as TokenTitleState
|
||||
|
||||
@Composable
|
||||
internal fun TokenTitle(state: TokenTitleState?, modifier: Modifier = Modifier) {
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
package com.tangem.core.ui.components.token.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
|
||||
/** TokenItem component state */
|
||||
@Immutable
|
||||
sealed class TokenItemState {
|
||||
|
||||
/** Unique id */
|
||||
abstract val id: String
|
||||
|
||||
/** Token icon state */
|
||||
abstract val iconState: CurrencyIconState
|
||||
|
||||
/** Token title state (in one row with [fiatAmountState]) */
|
||||
abstract val titleState: TitleState
|
||||
|
||||
/** Token subtitle state (under [titleState] and in one row with [cryptoAmountState]) */
|
||||
abstract val subtitleState: SubtitleState?
|
||||
|
||||
/** Token fiat amount state (in one row with [titleState]) */
|
||||
abstract val fiatAmountState: FiatAmountState?
|
||||
|
||||
/** Token crypto amount state (under [fiatAmountState] and in one row with [subtitleState]) */
|
||||
abstract val cryptoAmountState: CryptoAmountState?
|
||||
|
||||
/** Callback which will be called when an item is clicked */
|
||||
abstract val onItemClick: (() -> Unit)?
|
||||
|
||||
/** Callback which will be called when an item is long clicked */
|
||||
abstract val onItemLongClick: (() -> Unit)?
|
||||
|
||||
/**
|
||||
* Loading token state
|
||||
*
|
||||
* @property id unique id
|
||||
* @property iconState token icon state
|
||||
* @property titleState token title
|
||||
* @property subtitleState token subtitle
|
||||
*/
|
||||
data class Loading(
|
||||
override val id: String,
|
||||
override val iconState: CurrencyIconState,
|
||||
override val titleState: TitleState.Content,
|
||||
override val subtitleState: SubtitleState = SubtitleState.Loading,
|
||||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Loading
|
||||
override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Loading
|
||||
override val onItemClick: (() -> Unit)? = null
|
||||
override val onItemLongClick: (() -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Locked token state
|
||||
*
|
||||
* @property id unique id
|
||||
*/
|
||||
data class Locked(override val id: String) : TokenItemState() {
|
||||
override val iconState: CurrencyIconState = CurrencyIconState.Locked
|
||||
override val titleState: TitleState = TitleState.Locked
|
||||
override val subtitleState: SubtitleState = SubtitleState.Locked
|
||||
override val fiatAmountState: FiatAmountState = FiatAmountState.Locked
|
||||
override val cryptoAmountState: CryptoAmountState = CryptoAmountState.Locked
|
||||
override val onItemClick: (() -> Unit)? = null
|
||||
override val onItemLongClick: (() -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Content token state
|
||||
*
|
||||
* @property id unique id
|
||||
* @property iconState token icon state
|
||||
* @property titleState token title
|
||||
* @property subtitleState token subtitle
|
||||
* @property fiatAmountState token fiat amount
|
||||
* @property cryptoAmountState token crypto amount
|
||||
* @property onItemClick callback which will be called when an item is clicked
|
||||
* @property onItemLongClick callback which will be called when an item is long clicked
|
||||
*/
|
||||
data class Content(
|
||||
override val id: String,
|
||||
override val iconState: CurrencyIconState,
|
||||
override val titleState: TitleState,
|
||||
override val subtitleState: SubtitleState,
|
||||
override val fiatAmountState: FiatAmountState,
|
||||
override val cryptoAmountState: CryptoAmountState.Content,
|
||||
override val onItemClick: (() -> Unit)?,
|
||||
override val onItemLongClick: (() -> Unit)?,
|
||||
) : TokenItemState()
|
||||
|
||||
/**
|
||||
* Draggable token state
|
||||
*
|
||||
* @property id unique id
|
||||
* @property iconState token icon state
|
||||
* @property titleState token title
|
||||
* @property cryptoAmountState token crypto amount
|
||||
*/
|
||||
data class Draggable(
|
||||
override val id: String,
|
||||
override val iconState: CurrencyIconState,
|
||||
override val titleState: TitleState,
|
||||
override val cryptoAmountState: CryptoAmountState,
|
||||
) : TokenItemState() {
|
||||
override val subtitleState: SubtitleState? = null
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val onItemClick: (() -> Unit)? = null
|
||||
override val onItemLongClick: (() -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Unreachable token state
|
||||
*
|
||||
* @property id unique id
|
||||
* @property iconState token icon state
|
||||
* @property titleState token title
|
||||
* @property subtitleState token subtitle
|
||||
* @property onItemClick callback which will be called when an item is clicked
|
||||
* @property onItemLongClick callback which will be called when an item is long clicked
|
||||
*/
|
||||
data class Unreachable(
|
||||
override val id: String,
|
||||
override val iconState: CurrencyIconState,
|
||||
override val titleState: TitleState,
|
||||
override val subtitleState: SubtitleState? = null,
|
||||
override val onItemClick: (() -> Unit)?,
|
||||
override val onItemLongClick: (() -> Unit)?,
|
||||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val cryptoAmountState: CryptoAmountState? = null
|
||||
}
|
||||
|
||||
/**
|
||||
* No derivation address state
|
||||
*
|
||||
* @property id unique id
|
||||
* @property iconState token icon state
|
||||
* @property titleState token title
|
||||
* @property subtitleState token subtitle
|
||||
* @property onItemLongClick callback which will be called when an item is long clicked
|
||||
*/
|
||||
data class NoAddress(
|
||||
override val id: String,
|
||||
override val iconState: CurrencyIconState,
|
||||
override val titleState: TitleState,
|
||||
override val subtitleState: SubtitleState? = null,
|
||||
override val onItemLongClick: (() -> Unit)?,
|
||||
) : TokenItemState() {
|
||||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val cryptoAmountState: CryptoAmountState? = null
|
||||
override val onItemClick: (() -> Unit)? = null
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class TitleState {
|
||||
|
||||
data class Content(val text: String, val hasPending: Boolean = false) : TitleState()
|
||||
|
||||
data object Loading : TitleState()
|
||||
|
||||
data object Locked : TitleState()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class SubtitleState {
|
||||
|
||||
data class CryptoPriceContent(
|
||||
val price: String,
|
||||
val priceChangePercent: String,
|
||||
val type: PriceChangeType,
|
||||
) : SubtitleState()
|
||||
|
||||
data class TextContent(val value: String) : SubtitleState()
|
||||
|
||||
data object Unknown : SubtitleState()
|
||||
|
||||
data object Loading : SubtitleState()
|
||||
|
||||
data object Locked : SubtitleState()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class FiatAmountState {
|
||||
data class Content(
|
||||
val text: String,
|
||||
val hasStaked: Boolean = false,
|
||||
) : FiatAmountState()
|
||||
|
||||
data object Loading : FiatAmountState()
|
||||
|
||||
data object Locked : FiatAmountState()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class CryptoAmountState {
|
||||
data class Content(val text: String) : CryptoAmountState()
|
||||
|
||||
data object Unreachable : CryptoAmountState()
|
||||
|
||||
data object Loading : CryptoAmountState()
|
||||
|
||||
data object Locked : CryptoAmountState()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.core.ui.decompose
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
@Stable
|
||||
interface ComposableBottomSheetComponent {
|
||||
|
||||
fun dismiss()
|
||||
|
||||
@Composable
|
||||
fun BottomSheet()
|
||||
}
|
||||
|
|
@ -73,6 +73,7 @@ fun getActiveIconRes(blockchainId: String): Int {
|
|||
"blast", "blast/test" -> R.drawable.img_blast_22
|
||||
"filecoin" -> R.drawable.img_filecoin_22
|
||||
"cyber", "cyber/test" -> R.drawable.img_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.img_sei_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -147,6 +148,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int {
|
|||
"blast", "blast/test" -> R.drawable.img_blast_22
|
||||
"filecoin" -> R.drawable.img_filecoin_22
|
||||
"cyber", "cyber/test" -> R.drawable.img_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.img_sei_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -218,6 +220,7 @@ fun getActiveIconResByCoinId(coinId: String): Int {
|
|||
"blast", "blast/test" -> R.drawable.img_blast_22
|
||||
"filecoin" -> R.drawable.img_filecoin_22
|
||||
"cyber", "cyber/test" -> R.drawable.img_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.img_sei_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -292,6 +295,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
|
|||
"blast", "blast/test" -> R.drawable.ic_blast_22
|
||||
"filecoin" -> R.drawable.ic_filecoin_22
|
||||
"cyber", "cyber/test" -> R.drawable.ic_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.ic_sei_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
|
|
@ -366,6 +370,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int {
|
|||
"blast", "blast/test" -> R.drawable.ic_blast_22
|
||||
"filecoin" -> R.drawable.ic_filecoin_22
|
||||
"cyber", "cyber/test" -> R.drawable.ic_cyber_22
|
||||
"sei", "sei/test" -> R.drawable.ic_sei_22
|
||||
else -> R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
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