Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-07 14:26:11 +02:00
commit ac8436460c
136 changed files with 1636 additions and 1060 deletions

@ -1 +1 @@
Subproject commit cb7c080185a65b3b25cee30c331b696f784783f9
Subproject commit 095b8f4ea0fa02e7ccea93cf0f437346345297ef

View file

@ -0,0 +1,31 @@
package com.tangem.tap.common.analytics
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.utils.AnalyticsContextProxy
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.extensions.addContext
import com.tangem.tap.common.extensions.eraseContext
import com.tangem.tap.common.extensions.removeContext
import com.tangem.tap.common.extensions.setContext
/**
[REDACTED_AUTHOR]
*/
internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
override fun setContext(scanResponse: ScanResponse) {
Analytics.setContext(scanResponse)
}
override fun eraseContext() {
Analytics.eraseContext()
}
override fun addContext(scanResponse: ScanResponse) {
Analytics.addContext(scanResponse)
}
override fun removeContext() {
Analytics.removeContext()
}
}

View file

@ -1,8 +1,6 @@
package com.tangem.tap.common.url
import android.content.Context
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.Intent.FLAG_ACTIVITY_NO_HISTORY
import android.net.Uri
import androidx.browser.customtabs.CustomTabColorSchemeParams
import androidx.browser.customtabs.CustomTabsIntent
@ -36,9 +34,6 @@ internal class CustomTabsUrlOpener : UrlOpener {
)
.build()
// Open CustomTabsActivity as new task without saving into the stack
customTabsIntent.intent.setFlags(FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_NO_HISTORY)
customTabsIntent.launchUrl(context, Uri.parse(url))
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.tap.di.analytics
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 dagger.Module
import dagger.Provides
@ -17,4 +19,8 @@ internal object AnalyticsModule {
fun provideChangeCardAnalyticsContextUseCase(): ChangeCardAnalyticsContextUseCase {
return DefaultChangeCardAnalyticsContextUseCase()
}
@Provides
@Singleton
fun provideAnalyticsContextProxy(): AnalyticsContextProxy = DefaultAnalyticsContextProxy()
}

View file

@ -184,30 +184,6 @@ internal object SettingsDomainModule {
return NeverToInitiallyAskPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideIsFirstTimeAskingPermissionUseCase(
permissionRepository: PermissionRepository,
): IsFirstTimeAskingPermissionUseCase {
return IsFirstTimeAskingPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideSetFirstTimeAskingPushPermissionUseCase(
permissionRepository: PermissionRepository,
): SetFirstTimeAskingPermissionUseCase {
return SetFirstTimeAskingPermissionUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideDelayPermissionRequestUseCase(
permissionRepository: PermissionRepository,
): DelayPermissionRequestUseCase {
return DelayPermissionRequestUseCase(repository = permissionRepository)
}
@Provides
@Singleton
fun provideShouldAskPermissionUseCase(permissionRepository: PermissionRepository): ShouldAskPermissionUseCase {

View file

@ -1,12 +1,12 @@
package com.tangem.tap.di.domain
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.*
@ -95,8 +95,11 @@ internal object WalletsDomainModule {
@Provides
@Singleton
fun providesRenameWalletUseCase(userWalletsListManager: UserWalletsListManager): RenameWalletUseCase {
return RenameWalletUseCase(userWalletsListManager = userWalletsListManager)
fun providesRenameWalletUseCase(
userWalletsListManager: UserWalletsListManager,
dispatchers: CoroutineDispatcherProvider,
): RenameWalletUseCase {
return RenameWalletUseCase(userWalletsListManager = userWalletsListManager, dispatchers = dispatchers)
}
@Provides

View file

@ -32,6 +32,7 @@ import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
internal object LegacyScanProcessor {
@ -115,24 +116,28 @@ internal object LegacyScanProcessor {
} else {
scope.launch {
delay(DELAY_SDK_DIALOG_CLOSE)
disclaimerWillShow()
store.dispatchWithMain(
DisclaimerAction.Show(
from = DisclaimerSource.Home,
callback = DisclaimerCallback(
onAccept = {
scope.launch(Dispatchers.Main) {
nextHandler(scanResponse)
}
},
onDismiss = {
scope.launch(Dispatchers.Main) {
onFailure(TangemSdkError.UserCancelled())
}
},
withContext(Dispatchers.Main.immediate) {
disclaimerWillShow()
store.dispatch(
DisclaimerAction.Show(
from = DisclaimerSource.Home,
callback = DisclaimerCallback(
onAccept = {
scope.launch(Dispatchers.Main.immediate) {
nextHandler(scanResponse)
}
},
onDismiss = {
scope.launch(Dispatchers.Main.immediate) {
onFailure(TangemSdkError.UserCancelled())
}
},
),
),
),
)
)
}
}
}
}

View file

@ -176,9 +176,9 @@ class WalletConnectSdkHelper {
is Result.Success -> gasLimitResult.data.toBigDecimal().multiply(BigDecimal("1.2"))
is Result.Failure -> {
(gasLimitResult.error as? Throwable)?.let { Timber.e(it, "getGasLimit failed") }
BigDecimal(DEFAULT_MAX_GASLIMIT) // Set high gasLimit if not provided
DEFAULT_MAX_GASLIMIT.toBigDecimal() // Set high gasLimit if not provided
}
else -> BigDecimal(DEFAULT_MAX_GASLIMIT) // Set high gasLimit if not provided
else -> DEFAULT_MAX_GASLIMIT.toBigDecimal() // Set high gasLimit if not provided
}
}

View file

@ -35,7 +35,8 @@ internal class DefaultLegacyWalletConnectRepository(
private val _activeSessions: MutableSharedFlow<List<WalletConnectSession>> = MutableSharedFlow()
override val activeSessions: Flow<List<WalletConnectSession>> = _activeSessions
private var currentSessions: List<WalletConnectSession> = emptyList()
override var currentSessions: List<WalletConnectSession> = emptyList()
private set
/**
* @param projectId Project ID at https://cloud.walletconnect.com/

View file

@ -9,6 +9,8 @@ interface LegacyWalletConnectRepository {
val activeSessions: Flow<List<WalletConnectSession>>
val currentSessions: List<WalletConnectSession>
fun init(projectId: String)
fun setUserNamespaces(userNamespaces: Map<NetworkNamespace, List<Account>>)

View file

@ -132,6 +132,7 @@ class WalletConnectInteractor(
runCatching {
if (accounts.isEmpty()) return
isWalletConnectReadyForDeepLinks = true
if (deeplinkStack.empty()) return
val lastDeeplink = deeplinkStack.pop()
store.dispatchOnMain(WalletConnectAction.OpenSession(lastDeeplink))
}.onFailure {
@ -361,6 +362,19 @@ class WalletConnectInteractor(
* @param deeplink deeplink to handle
*/
fun addDeeplink(deeplink: String) {
val deeplinkRegex = Regex(WC_PARAM_REGEX)
val matched = deeplinkRegex.findAll(deeplink)
val sessionTopic = matched.firstOrNull { it.value.contains(WC_TOPIC_QUERY_NAME) }?.groupValues?.lastOrNull()
val isAlreadyActiveSessionTopic = walletConnectRepository.currentSessions.any { session ->
session.topic == sessionTopic
}
if (isAlreadyActiveSessionTopic && sessionTopic != null) {
Timber.i("WC already has an active session topic: $deeplink")
return
}
if (isWalletConnectReadyForDeepLinks) {
store.dispatchOnMain(WalletConnectAction.OpenSession(deeplink))
} else {
@ -406,7 +420,9 @@ class WalletConnectInteractor(
return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId)
}
companion object {
private const val WC_SCHEME = "wc"
private companion object {
const val WC_SCHEME = "wc"
const val WC_TOPIC_QUERY_NAME = "sessionTopic"
const val WC_PARAM_REGEX = "([a-zA-Z\\d-]+)=([a-zA-Z\\d]+)"
}
}

View file

@ -5,11 +5,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -25,12 +22,6 @@ internal class CardSettingsFragment : ComposeFragment() {
override fun ScreenContent(modifier: Modifier) {
val state by viewModel.screenState.collectAsStateWithLifecycle()
CardSettingsScreen(
modifier = modifier,
state = state,
onBackClick = {
store.dispatchNavigationAction(AppRouter::pop)
},
)
CardSettingsScreen(modifier = modifier, state = state)
}
}

View file

@ -1,10 +1,13 @@
package com.tangem.tap.features.details.ui.cardsettings
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -13,18 +16,14 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.common.DetailsMainButton
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
import com.tangem.wallet.R
@Composable
internal fun CardSettingsScreen(
state: CardSettingsScreenState,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
internal fun CardSettingsScreen(state: CardSettingsScreenState, modifier: Modifier = Modifier) {
val needReadCard = state.cardDetails == null
SettingsScreensScaffold(
@ -37,7 +36,7 @@ internal fun CardSettingsScreen(
}
},
titleRes = R.string.card_settings_title,
onBackClick = onBackClick,
onBackClick = state.onBackClick,
)
}
@ -180,7 +179,7 @@ private fun CardSettings(state: CardSettingsScreenState) {
// region Preview
@Composable
private fun CardSettingsScreenStateSample() {
CardSettingsScreen(state = CardSettingsScreenState(onScanCardClick = {}, onElementClick = {}), {})
CardSettingsScreen(state = CardSettingsScreenState(onBackClick = {}, onScanCardClick = {}, onElementClick = {}))
}
@Preview(showBackground = true, widthDp = 360)

View file

@ -12,6 +12,7 @@ internal data class CardSettingsScreenState(
val cardDetails: List<CardInfo>? = null,
val onScanCardClick: () -> Unit,
val onElementClick: (CardInfo) -> Unit,
val onBackClick: () -> Unit,
)
internal sealed class CardInfo(

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
import com.tangem.common.CompletionResult
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.bundle.unbundle
import com.tangem.core.analytics.Analytics
import com.tangem.domain.card.ScanCardProcessor
@ -23,12 +24,14 @@ import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.*
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.store
import com.tangem.wallet.R
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -37,6 +40,7 @@ import javax.inject.Inject
internal class CardSettingsViewModel @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val tangemSdkManager: TangemSdkManager,
private val cardSettingsInteractor: CardSettingsInteractor,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
@ -44,24 +48,28 @@ internal class CardSettingsViewModel @Inject constructor(
?.unbundle(UserWalletId.serializer())
?: error("User wallet ID is required for CardSettingsViewModel")
private val scannedScanResponse = MutableStateFlow<ScanResponse?>(value = null)
val screenState: MutableStateFlow<CardSettingsScreenState> = MutableStateFlow(getInitialState())
init {
cardSettingsInteractor.scannedScanResponse
.filterNotNull()
.onEach(::updateCardDetails)
.launchIn(viewModelScope)
}
private fun getInitialState() = CardSettingsScreenState(
cardDetails = null,
onElementClick = ::handleClickingItem,
onScanCardClick = ::scanCard,
onBackClick = ::onBackClick,
)
private fun scanCard() = viewModelScope.launch {
scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true)
.doOnSuccess { scanResponse ->
scannedScanResponse.value = scanResponse
val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
if (userWalletId == scannedUserWalletId || scannedUserWalletId == null) {
updateCardDetails(scanResponse)
cardSettingsInteractor.initialize(scanResponse)
} else {
store.dispatchDialogShow(
AppDialog.SimpleOkDialogRes(
@ -129,27 +137,8 @@ internal class CardSettingsViewModel @Inject constructor(
changeAccessCode()
}
is CardInfo.ResetToFactorySettings -> {
val card = requireNotNull(scannedScanResponse.value) {
"Impossible to reset card if ScanResponse is null"
}.card
Analytics.send(Settings.CardSettings.ButtonFactoryReset())
store.dispatchNavigationAction {
push(
route = AppRoute.ResetToFactory(
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
},
),
)
}
resetWalletToFactorySettings()
}
is CardInfo.SecurityMode -> {
Analytics.send(Settings.CardSettings.ButtonChangeSecurityMode())
@ -158,16 +147,47 @@ internal class CardSettingsViewModel @Inject constructor(
}
}
is CardInfo.AccessCodeRecovery -> {
store.dispatchNavigationAction {
push(route = AppRoute.AccessCodeRecovery(userWalletId))
}
store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) }
}
else -> {}
}
}
private fun resetWalletToFactorySettings() {
val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) {
"Impossible to reset card if ScanResponse is null"
}
if (scanResponse.cardTypesResolver.isTangemTwins()) {
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse)))
cardSettingsInteractor.clear()
store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) }
} else {
val card = scanResponse.card
store.dispatchNavigationAction {
push(
route = AppRoute.ResetToFactory(
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
},
),
)
}
}
}
private fun changeAccessCode() = viewModelScope.launch {
val scanResponse = requireNotNull(scannedScanResponse.value) { "Scan response is null" }
val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) { "Scan response is null" }
when (val result = tangemSdkManager.setAccessCode(scanResponse.card.cardId)) {
is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged())
@ -182,4 +202,9 @@ internal class CardSettingsViewModel @Inject constructor(
val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin()
return !isNotAllowed
}
private fun onBackClick() {
cardSettingsInteractor.clear()
store.dispatchNavigationAction(AppRouter::pop)
}
}

View file

@ -1,23 +1,16 @@
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
import android.os.Bundle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.bundle.unbundle
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled
import com.tangem.tap.store
import dagger.hilt.android.lifecycle.HiltViewModel
@ -28,25 +21,21 @@ import javax.inject.Inject
@HiltViewModel
internal class AccessCodeRecoveryViewModel @Inject constructor(
private val getUserWalletUseCase: GetUserWalletUseCase,
private val tangemSdkManager: TangemSdkManager,
savedStateHandle: SavedStateHandle,
private val cardSettingsInteractor: CardSettingsInteractor,
) : ViewModel() {
private val userWalletId = savedStateHandle.get<Bundle>(AppRoute.AccessCodeRecovery.USER_WALLET_ID_KEY)
?.unbundle(UserWalletId.serializer())
?: error("UserWalletId is required for AccessCodeRecoveryViewModel")
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
?: error("Scan response is null")
val screenState = MutableStateFlow(
value = getInitialState(),
)
private fun getInitialState(): AccessCodeRecoveryScreenState {
val userWallet = getUserWallet()
val isEnabled = isAccessCodeRecoveryEnabled(
typeResolver = userWallet.scanResponse.cardTypesResolver,
card = userWallet.scanResponse.card,
typeResolver = scannedScanResponse.cardTypesResolver,
card = scannedScanResponse.card,
)
return AccessCodeRecoveryScreenState(
@ -59,11 +48,10 @@ internal class AccessCodeRecoveryViewModel @Inject constructor(
}
private fun saveChanges() = viewModelScope.launch {
val userWallet = getUserWallet()
val isEnabled = screenState.value.enabledSelection
tangemSdkManager
.setAccessCodeRecoveryEnabled(userWallet.cardId, isEnabled)
.setAccessCodeRecoveryEnabled(scannedScanResponse.card.cardId, isEnabled)
.doOnSuccess {
Analytics.send(
Settings.CardSettings.AccessCodeRecoveryChanged(
@ -71,6 +59,16 @@ internal class AccessCodeRecoveryViewModel @Inject constructor(
),
)
cardSettingsInteractor.update { scanResponse ->
scanResponse.copy(
card = scanResponse.card.copy(
userSettings = scanResponse.card.userSettings?.copy(
isUserCodeRecoveryAllowed = isEnabled,
),
),
)
}
store.dispatchNavigationAction(AppRouter::pop)
}
}
@ -78,14 +76,9 @@ internal class AccessCodeRecoveryViewModel @Inject constructor(
private fun selectOption(isEnabled: Boolean) {
screenState.update {
it.copy(
enabledSelection = isEnabled,
isSaveChangesEnabled = isEnabled != it.enabledOnCard,
)
}
}
private fun getUserWallet(): UserWallet {
return getUserWalletUseCase(userWalletId).getOrElse {
error("Unable to get user wallet $userWalletId: $it")
}
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.tap.features.details.ui.cardsettings.domain
import com.tangem.domain.models.scan.ScanResponse
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import javax.inject.Inject
import javax.inject.Singleton
/**
* Interactor for sharing logic and data between all card settings screens
*
[REDACTED_AUTHOR]
*/
@Singleton
internal class CardSettingsInteractor @Inject constructor() {
private val _scannedScanResponse = MutableStateFlow<ScanResponse?>(value = null)
val scannedScanResponse: StateFlow<ScanResponse?> = _scannedScanResponse
fun initialize(scanResponse: ScanResponse) {
_scannedScanResponse.value = scanResponse
}
fun update(transform: (ScanResponse) -> ScanResponse) {
_scannedScanResponse.update {
requireNotNull(it)
transform(it)
}
}
fun clear() {
_scannedScanResponse.value = null
}
}

View file

@ -60,7 +60,7 @@ internal sealed class SettingsItem(
data class LinkMoreCards(
override val onClick: () -> Unit,
) : SettingsItem(
iconResId = R.drawable.ic_more_cards,
iconResId = R.drawable.ic_more_cards_24,
title = resourceReference(R.string.details_row_title_create_backup),
)

View file

@ -23,6 +23,7 @@ import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.features.details.redux.ResetCardDialog
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription
import com.tangem.tap.store
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
@ -44,6 +45,7 @@ internal class ResetCardViewModel @Inject constructor(
private val deleteWalletUseCase: DeleteWalletUseCase,
private val userWalletsListManager: UserWalletsListManager,
private val analyticsEventHandler: AnalyticsEventHandler,
private val cardSettingsInteractor: CardSettingsInteractor,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
@ -255,6 +257,8 @@ internal class ResetCardViewModel @Inject constructor(
}
private fun finishFullReset() {
cardSettingsInteractor.clear()
val newSelectedWallet = userWalletsListManager.selectedUserWalletSync
if (newSelectedWallet != null) {

View file

@ -6,14 +6,13 @@ import android.net.Uri
[REDACTED_AUTHOR]
*/
interface Disclaimer {
fun type(): DisclaimerType
fun getUri(): Uri
suspend fun accept()
suspend fun isAccepted(): Boolean
}
abstract class BaseDisclaimer(
protected val dataProvider: DisclaimerDataProvider,
private val dataProvider: DisclaimerDataProvider,
) : Disclaimer {
val baseUrl = "https://tangem.com"
@ -26,46 +25,11 @@ abstract class BaseDisclaimer(
}
class DummyDisclaimer : Disclaimer {
override fun type(): DisclaimerType = DisclaimerType.Tangem
override fun getUri(): Uri = Uri.parse("https://tangem.com/tangem_tos.html")
override suspend fun accept() {}
override suspend fun isAccepted(): Boolean = false
}
class TangemDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) {
override fun type(): DisclaimerType = DisclaimerType.Tangem
override fun getUri(): Uri = Uri.parse("$baseUrl/tangem_tos.html")
}
class Start2CoinDisclaimer(dataProvider: DisclaimerDataProvider) : BaseDisclaimer(dataProvider) {
override fun type(): DisclaimerType = DisclaimerType.Start2Coin
override fun getUri(): Uri = Uri.parse("$baseUrl/" + filename(dataProvider.getLanguage(), getRegion()))
@Suppress("ComplexMethod")
private fun filename(languageCode: String, regionCode: String?): String {
return when {
languageCode == "fr" && regionCode == "ch" -> "start2coin-fr-ch-tangem.html"
languageCode == "de" && regionCode == "ch" -> "start2coin-de-ch-tangem.html"
languageCode == "en" && regionCode == "ch" -> "start2coin-en-ch-tangem.html"
languageCode == "it" && regionCode == "ch" -> "start2coin-it-ch-tangem.html"
languageCode == "fr" && regionCode == "fr" -> "start2coin-fr-fr-tangem.html"
languageCode == "de" && regionCode == "at" -> "start2coin-de-at-tangem.html"
regionCode == "fr" -> "start2coin-fr-fr-tangem.html"
regionCode == "ch" -> "start2coin-en-ch-tangem.html"
regionCode == "at" -> "start2coin-de-at-tangem.html"
else -> "start2coin-fr-fr-tangem.html"
}
}
private fun getRegion(): String? {
val cardId = dataProvider.getCardId()
if (cardId.isEmpty()) return null
return when (cardId[1]) {
'0' -> "fr"
'1' -> "ch"
'2' -> "at"
else -> null
}
}
}

View file

@ -1,58 +1,28 @@
package com.tangem.tap.features.disclaimer
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.store
import java.util.Locale
/**
[REDACTED_AUTHOR]
*/
enum class DisclaimerType {
Tangem,
Start2Coin,
;
companion object {
fun get(cardDTO: CardDTO): DisclaimerType {
return when {
cardDTO.isStart2Coin -> Start2Coin
else -> Tangem
}
}
}
fun CardDTO.createDisclaimer(): Disclaimer {
val dataProvider = provideDisclaimerDataProvider(cardId)
return TangemDisclaimer(dataProvider)
}
fun DisclaimerType.createDisclaimer(cardDTO: CardDTO): Disclaimer {
val dataProvider = provideDisclaimerDataProvider(cardDTO.cardId, this)
return when (this) {
DisclaimerType.Tangem -> TangemDisclaimer(dataProvider)
DisclaimerType.Start2Coin -> Start2CoinDisclaimer(dataProvider)
}
}
fun CardDTO.createDisclaimer(): Disclaimer = DisclaimerType.get(this).createDisclaimer(this)
private fun provideDisclaimerDataProvider(cardId: String, disclaimerType: DisclaimerType): DisclaimerDataProvider {
private fun provideDisclaimerDataProvider(cardId: String): DisclaimerDataProvider {
val cardRepository = store.inject(DaggerGraphState::cardRepository)
return object : DisclaimerDataProvider {
override fun getLanguage(): String = Locale.getDefault().language
override fun getCardId(): String = cardId
override suspend fun accept() {
when (disclaimerType) {
DisclaimerType.Tangem -> cardRepository.acceptTangemTOS()
DisclaimerType.Start2Coin -> cardRepository.acceptStart2CoinTOS(cardId)
}
cardRepository.acceptTangemTOS()
}
override suspend fun isAccepted(): Boolean {
return when (disclaimerType) {
DisclaimerType.Tangem -> cardRepository.isTangemTOSAccepted()
DisclaimerType.Start2Coin -> cardRepository.isStart2CoinTOSAccepted(cardId)
}
return cardRepository.isTangemTOSAccepted()
}
}
}

View file

@ -1,5 +1,7 @@
package com.tangem.tap.features.home.redux
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.tangem.common.doOnFailure
import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
@ -61,8 +63,13 @@ private fun handleHomeAction(action: Action) {
}
is HomeAction.GoToShop -> {
Analytics.send(Shop.ScreenOpened())
store.dispatchOpenUrl(NEW_BUY_WALLET_URL)
Firebase.analytics.appInstanceId
.addOnSuccessListener {
store.dispatchOpenUrl("$NEW_BUY_WALLET_URL&app_instance_id=$it")
}
.addOnFailureListener {
store.dispatchOpenUrl(NEW_BUY_WALLET_URL)
}
// disabled for now in task [REDACTED_JIRA]
// when (action.userCountryCode) {
// RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> store.dispatchOpenUrl(BUY_WALLET_URL)

View file

@ -32,26 +32,27 @@ import timber.log.Timber
*/
object OnboardingHelper {
suspend fun isOnboardingCase(response: ScanResponse): Boolean {
val onboardingManager = store.state.globalState.onboardingState.onboardingManager
val onboardingManager =
store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response)
val cardId = response.card.cardId
return when {
response.cardTypesResolver.isTangemTwins() -> {
if (!response.twinsIsTwinned()) {
true
} else {
onboardingManager?.isActivationInProgress(cardId) ?: false
onboardingManager.isActivationInProgress(cardId) ?: false
}
}
response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> {
val emptyWallets = response.card.wallets.isEmpty()
val activationInProgress = onboardingManager?.isActivationInProgress(cardId)
val activationInProgress = onboardingManager.isActivationInProgress(cardId)
val isNoBackup = response.card.backupStatus == CardDTO.BackupStatus.NoBackup &&
!DemoHelper.isDemoCard(response)
emptyWallets || activationInProgress == true || isNoBackup
emptyWallets || activationInProgress || isNoBackup
}
response.card.wallets.isNotEmpty() -> onboardingManager?.isActivationInProgress(cardId) ?: false
response.card.wallets.isNotEmpty() -> onboardingManager.isActivationInProgress(cardId) ?: false
else -> true
}
}

View file

@ -11,12 +11,14 @@ import coil.load
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.ShareElement
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.analytics.events.Onboarding
import com.tangem.tap.common.extensions.getDrawableCompat
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
@ -134,7 +136,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
}
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
if (state.isBuyAllowed) {
if (availableForBuy(state.scanResponse, state.walletBalance)) {
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
btnMainAction.icon = null
btnMainAction.setOnClickListener {
@ -217,6 +219,11 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
}
}
private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean {
scanResponse ?: return false
return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency)
}
override fun handleOnBackPressed() {
store.dispatch(OnboardingNoteAction.OnBackPressed)
}

View file

@ -13,7 +13,7 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt
when (action) {
is GlobalAction.Onboarding.Start -> {
state = OnboardingNoteState()
state = OnboardingNoteState(scanResponse = action.scanResponse)
}
is OnboardingNoteAction.SetArtworkUrl -> {
state = state.copy(cardArtworkUrl = action.artworkUrl)

View file

@ -1,11 +1,10 @@
package com.tangem.tap.features.onboarding.products.note.redux
import com.tangem.blockchain.common.WalletManager
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.TapError
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import com.tangem.tap.store
import org.rekotlin.StateType
import kotlin.properties.ReadOnlyProperty
/**
[REDACTED_AUTHOR]
@ -20,14 +19,11 @@ data class OnboardingNoteState(
val currentStep: OnboardingNoteStep = OnboardingNoteStep.None,
val steps: List<OnboardingNoteStep> = OnboardingNoteStep.values().toList(),
val showConfetti: Boolean = false,
val scanResponse: ScanResponse? = null,
) : StateType {
val progress: Int
get() = steps.indexOf(currentStep)
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { _, _ ->
store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency)
}
}
enum class OnboardingNoteStep {

View file

@ -46,7 +46,7 @@ object TwinCardsMiddleware {
val handler = twinsWalletMiddleware
}
private val twinsWalletMiddleware: Middleware<AppState> = { dispatch, state ->
private val twinsWalletMiddleware: Middleware<AppState> = { dispatch, _ ->
{ next ->
{ action ->
handle(action, dispatch)
@ -66,15 +66,15 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
fun getScanResponse(): ScanResponse {
return when (twinCardsState.mode) {
CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse
CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse
is CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse
} ?: throw NullPointerException("ScanResponse can't be NULL")
}
fun updateScanResponse(response: ScanResponse) {
when (twinCardsState.mode) {
CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response))
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
is CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response))
}
}
@ -105,6 +105,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
mainScope.launch {
if (twinCardsState.currentStep is TwinCardsStep.WelcomeOnly) return@launch
if (twinCardsState.mode is CreateTwinWalletMode.RecreateWallet) {
store.dispatch(GlobalAction.SaveScanResponse(twinCardsState.mode.scanResponse))
}
val scanResponse = getScanResponse()
onboardingManager?.apply {
if (!isActivationStarted(scanResponse.card.cardId)) {
@ -113,7 +117,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
}
when (twinCardsState.mode) {
CreateTwinWalletMode.CreateWallet -> {
is CreateTwinWalletMode.CreateWallet -> {
mainScope.launch {
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
.invokeSync()
@ -132,7 +136,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
store.dispatch(dispatchAction)
}
}
CreateTwinWalletMode.RecreateWallet -> {
is CreateTwinWalletMode.RecreateWallet -> {
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning))
}
}
@ -226,10 +230,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
delay(DELAY_SDK_DIALOG_CLOSE)
withMainContext {
when (twinCardsState.mode) {
CreateTwinWalletMode.CreateWallet -> {
is CreateTwinWalletMode.CreateWallet -> {
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet))
}
CreateTwinWalletMode.RecreateWallet -> {
is CreateTwinWalletMode.RecreateWallet -> {
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done))
}
}
@ -319,11 +323,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
TwinCardsAction.Done -> {
val scanResponse = getScanResponse()
when (twinCardsState.mode) {
CreateTwinWalletMode.CreateWallet -> {
is CreateTwinWalletMode.CreateWallet -> {
store.dispatchOnMain(GlobalAction.Onboarding.Stop)
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
}
CreateTwinWalletMode.RecreateWallet -> {
is CreateTwinWalletMode.RecreateWallet -> {
scope.launch {
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
@ -368,8 +372,7 @@ private fun getPopBackScreen(): KClass<out AppRoute> {
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
return if (userWalletsListManager.hasUserWallets) {
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
.fold(onSuccess = { true }, onFailure = { false })
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync!! }.getOrElse { false }
if (isLocked) {
AppRoute.Welcome::class

View file

@ -6,9 +6,7 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.twins.TwinCardsManager
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import com.tangem.tap.store
import org.rekotlin.StateType
import kotlin.properties.ReadOnlyProperty
/**
[REDACTED_AUTHOR]
@ -34,7 +32,7 @@ data class TwinCardsState(
val steps: List<TwinCardsStep>
get() = when (mode) {
CreateTwinWalletMode.CreateWallet -> listOf(
is CreateTwinWalletMode.CreateWallet -> listOf(
TwinCardsStep.None,
TwinCardsStep.CreateFirstWallet,
TwinCardsStep.CreateSecondWallet,
@ -42,7 +40,7 @@ data class TwinCardsState(
TwinCardsStep.TopUpWallet,
TwinCardsStep.Done,
)
CreateTwinWalletMode.RecreateWallet -> listOf(
is CreateTwinWalletMode.RecreateWallet -> listOf(
TwinCardsStep.None,
TwinCardsStep.CreateFirstWallet,
TwinCardsStep.CreateSecondWallet,
@ -56,13 +54,15 @@ data class TwinCardsState(
val twinningInProgress: Boolean
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { _, _ ->
store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency)
}
}
enum class CreateTwinWalletMode { CreateWallet, RecreateWallet }
sealed class CreateTwinWalletMode {
data object CreateWallet : CreateTwinWalletMode()
data class RecreateWallet(
val scanResponse: ScanResponse,
) : CreateTwinWalletMode()
}
sealed class TwinCardsStep {
object None : TwinCardsStep()

View file

@ -27,6 +27,7 @@ import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
import com.tangem.tap.domain.twins.TwinsCardWidget
import com.tangem.tap.features.addBackPressHandler
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
@ -55,22 +56,17 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
override fun configureTransitions() {
when (store.state.twinCardsState.mode) {
CreateTwinWalletMode.CreateWallet -> {
is CreateTwinWalletMode.CreateWallet -> {
super.configureTransitions()
}
CreateTwinWalletMode.RecreateWallet -> {
is CreateTwinWalletMode.RecreateWallet -> {
configureDefaultTransactions()
}
}
}
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
scanResponseProvider = Provider {
store.state.twinCardsState.welcomeOnlyScanResponse
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
?: store.state.detailsState.scanResponse
?: error("ScanResponse must be not null")
},
scanResponseProvider = Provider { getActualScanResponse() },
)
@Suppress("MagicNumber")
@ -342,7 +338,7 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
else -> {}
}
if (state.isBuyAllowed) {
if (availableForBuy(getActualScanResponse(), state.walletBalance)) {
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
btnMainAction.setOnClickListener {
store.dispatch(TwinCardsAction.TopUp)
@ -395,8 +391,8 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
tvBody.setText(R.string.onboarding_done_body)
val layout = when (state.mode) {
CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
is CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
is CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
}
updateConstraints(state.currentStep, layout)
}
@ -424,6 +420,18 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
}
}
private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean {
scanResponse ?: return false
return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency)
}
private fun getActualScanResponse(): ScanResponse {
return store.state.twinCardsState.welcomeOnlyScanResponse
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
?: store.state.detailsState.scanResponse
?: error("ScanResponse must be not null")
}
override fun handleOnBackPressed() {
store.dispatch(
TwinCardsAction.OnBackPressed { should, popAction ->

View file

@ -35,6 +35,7 @@ import com.tangem.tap.features.onboarding.OnboardingDialog
import com.tangem.tap.features.onboarding.OnboardingHelper
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.wallet.R
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
@ -44,6 +45,8 @@ object OnboardingWalletMiddleware {
val handler = onboardingWalletMiddleware
}
private const val HIDE_PROGRESS_DELAY = 400L
private val onboardingWalletMiddleware: Middleware<AppState> = { dispatch, state ->
{ next ->
{ action ->
@ -161,17 +164,13 @@ private fun handleWalletAction(action: Action) {
store.dispatch(GlobalAction.Onboarding.Stop)
if (scanResponse == null) {
store.dispatchNavigationAction(AppRouter::pop)
store.dispatch(HomeAction.ReadCard(scope = action.scope))
action.scope.launch {
readCard { newScanResponse ->
handleFinishOnboardind(newScanResponse)
}
}
} else {
val backupState = store.state.onboardingWalletState.backupState
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(
scanResponse = updatedScanResponse,
accessCode = backupState.accessCode,
backupCardsIds = backupState.backupCardIds,
hasBackupError = backupState.hasBackupError,
)
handleFinishOnboardind(scanResponse)
}
}
is OnboardingWalletAction.ResumeBackup -> {
@ -196,6 +195,43 @@ private fun handleWalletAction(action: Action) {
}
}
private fun handleFinishOnboardind(scanResponse: ScanResponse) {
val backupState = store.state.onboardingWalletState.backupState
val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState)
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(
scanResponse = updatedScanResponse,
accessCode = backupState.accessCode,
backupCardsIds = backupState.backupCardIds,
hasBackupError = backupState.hasBackupError,
)
}
private suspend fun readCard(onSuccess: (ScanResponse) -> Unit) {
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = shouldSaveAccessCodes,
)
store.inject(DaggerGraphState::scanCardProcessor).scan(
analyticsSource = com.tangem.core.analytics.models.AnalyticsParam.ScreensSources.Intro,
onProgressStateChange = { showProgress ->
if (showProgress) {
store.dispatch(HomeAction.ScanInProgress(scanInProgress = true))
} else {
delay(HIDE_PROGRESS_DELAY)
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
}
},
onFailure = {
Timber.e(it, "Unable to scan card")
delay(HIDE_PROGRESS_DELAY)
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
},
onSuccess = onSuccess,
)
}
private suspend fun loadArtworkForUnfinishedBackup(
cardId: String,
cardPublicKey: ByteArray,

View file

@ -2,13 +2,7 @@ package com.tangem.tap.features.saveWallet.ui.components
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
@ -20,16 +14,10 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.SpacerH4
import com.tangem.core.ui.components.SpacerHHalf
import com.tangem.core.ui.components.SpacerW24
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.wallet.R
@Composable
@ -37,10 +25,7 @@ internal fun SaveWalletScreenContent(showProgress: Boolean, onSaveWalletClick: (
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Header(onCloseClick = onCloseClick)
SpacerHHalf()
Title(
modifier = Modifier
.widthIn(max = TangemTheme.dimens.size200),
)
Title(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing22))
SpacerH32()
Description(
modifier = Modifier

View file

@ -4,7 +4,10 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Icon
import androidx.compose.material.Text
@ -123,14 +126,14 @@ internal fun HasMoreItem(moreCount: Int) {
) {
Text(
modifier = Modifier
.padding(TangemTheme.dimens.spacing4)
.align(Alignment.Center)
.drawWithContent { if (readyToDraw) drawContent() },
text = "+$count",
style = textStyle,
color = TangemTheme.colors.text.tertiary,
overflow = TextOverflow.Clip,
onTextLayout = { textLayoutResult ->
if (textLayoutResult.didOverflowHeight) {
if (textLayoutResult.hasVisualOverflow) {
textStyle = textStyle.copy(fontSize = textStyle.fontSize * 0.9)
} else {
readyToDraw = true

View file

@ -1,5 +1,6 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService
@ -32,7 +33,8 @@ internal class BuyExchangeService(
override fun isSellAllowed(): Boolean = currentService.isSellAllowed()
override fun availableForBuy(currency: Currency): Boolean = currentService.availableForBuy(currency)
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean =
currentService.availableForBuy(scanResponse, currency)
override fun availableForSell(currency: Currency): Boolean = currentService.availableForSell(currency)

View file

@ -2,6 +2,7 @@ package com.tangem.tap.network.exchangeServices
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.features.demo.isDemoCard
@ -38,8 +39,8 @@ class CardExchangeRules(
}
}
override fun availableForBuy(currency: Currency): Boolean {
val card = cardProvider() ?: return false
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
val card = scanResponse.card
return when {
card.isDemoCard() -> true

View file

@ -8,6 +8,7 @@ import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.common.extensions.inject
import com.tangem.tap.common.extensions.safeUpdate
@ -38,8 +39,9 @@ class CurrencyExchangeManager(
override fun isBuyAllowed(): Boolean = primaryRules.isBuyAllowed() && buyService.isBuyAllowed()
override fun isSellAllowed(): Boolean = primaryRules.isSellAllowed() && sellService.isSellAllowed()
override fun availableForBuy(currency: Currency): Boolean {
return primaryRules.availableForBuy(currency) && buyService.availableForBuy(currency)
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
return primaryRules.availableForBuy(scanResponse, currency) &&
buyService.availableForBuy(scanResponse, currency)
}
override fun availableForSell(currency: Currency): Boolean {

View file

@ -1,13 +1,15 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
class DefaultRampManager(private val exchangeService: ExchangeService?) : RampStateManager {
private val cryptoCurrencyConverter = CryptoCurrencyConverter()
override fun availableForBuy(cryptoCurrency: CryptoCurrency): Boolean {
override fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean {
return exchangeService?.availableForBuy(
scanResponse,
currency = cryptoCurrencyConverter.convertBack(cryptoCurrency),
) ?: false
}

View file

@ -1,5 +1,6 @@
package com.tangem.tap.network.exchangeServices
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.common.feature.Feature
import com.tangem.tap.domain.model.Currency
@ -7,7 +8,7 @@ import com.tangem.tap.domain.model.Currency
interface Exchanger {
fun isBuyAllowed(): Boolean
fun isSellAllowed(): Boolean
fun availableForBuy(currency: Currency): Boolean
fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean
fun availableForSell(currency: Currency): Boolean
}
@ -20,7 +21,7 @@ interface ExchangeService : Feature, Exchanger, ExchangeUrlBuilder {
override suspend fun update() {}
override fun isBuyAllowed(): Boolean = false
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(currency: Currency): Boolean = false
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean = false
override fun getUrl(
action: CurrencyExchangeManager.Action,
@ -45,7 +46,7 @@ interface ExchangeRules : Feature, Exchanger {
override fun featureIsSwitchedOn(): Boolean = false
override fun isBuyAllowed(): Boolean = false
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(currency: Currency): Boolean = false
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean = false
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.common.extensions.calculateSha512
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
@ -28,7 +29,7 @@ internal class MercuryoService(private val environment: MercuryoEnvironment) : E
override fun isSellAllowed(): Boolean = false
override fun availableForBuy(currency: Currency): Boolean {
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean {
if (!isBuyAllowed()) return false
val mercuryoNetwork = currency.blockchain.mercuryoNetwork()

View file

@ -7,6 +7,7 @@ import com.tangem.common.services.Result
import com.tangem.common.services.performRequest
import com.tangem.datasource.api.common.createRetrofitInstance
import com.tangem.domain.common.extensions.withIOContext
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.tap.domain.model.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
@ -83,7 +84,7 @@ class MoonPayService(
return status?.responseUserStatus?.isSellAllowed ?: false
}
override fun availableForBuy(currency: Currency): Boolean = false
override fun availableForBuy(scanResponse: ScanResponse, currency: Currency): Boolean = false
override fun availableForSell(currency: Currency): Boolean {
if (!isSellAllowed()) return false

View file

@ -39,7 +39,7 @@ class TransactionManagerImpl(
@Throws(IllegalStateException::class)
override suspend fun getFee(
networkId: String,
amountToSend: BigDecimal,
amountToSend: Amount,
currencyToSend: Currency,
destinationAddress: String,
increaseBy: Int?,
@ -52,7 +52,7 @@ class TransactionManagerImpl(
if (walletManager is EthereumOptimisticRollupWalletManager) {
return getFeeForOptimismBlockchain(
walletManager = walletManager,
amount = createAmount(amountToSend, currencyToSend, blockchain),
amount = amountToSend,
destinationAddress = destinationAddress,
data = data,
)
@ -61,7 +61,6 @@ class TransactionManagerImpl(
walletManager = walletManager,
blockchain = blockchain,
amountToSend = amountToSend,
currency = currencyToSend,
destinationAddress = destinationAddress,
data = data,
increaseBy = increaseBy,
@ -70,8 +69,6 @@ class TransactionManagerImpl(
return getFeeForBlockchain(
walletManager = walletManager,
amountToSend = amountToSend,
currency = currencyToSend,
blockchain = blockchain,
destinationAddress = destinationAddress,
)
}
@ -87,13 +84,11 @@ class TransactionManagerImpl(
private suspend fun getFeeForBlockchain(
walletManager: WalletManager,
amountToSend: BigDecimal,
currency: Currency,
blockchain: Blockchain,
amountToSend: Amount,
destinationAddress: String,
): ProxyFees {
val fee = (walletManager as? TransactionSender)?.getFee(
amount = createAmount(amountToSend, currency, blockchain),
amount = amountToSend,
destination = destinationAddress,
) ?: error("Cannot cast to TransactionSender")
return when (fee) {
@ -155,17 +150,14 @@ class TransactionManagerImpl(
private suspend fun getFeeForEthereumBlockchain(
walletManager: EthereumWalletManager,
blockchain: Blockchain,
amountToSend: BigDecimal,
currency: Currency,
amountToSend: Amount,
destinationAddress: String,
data: String?,
increaseBy: Int?,
): ProxyFees {
val gasLimit = getGasLimit(
evmWalletManager = walletManager,
blockchain = blockchain,
amount = amountToSend,
currency = currency,
destinationAddress = destinationAddress,
data = data,
).increaseBigIntegerByPercents(increaseBy)
@ -219,23 +211,20 @@ class TransactionManagerImpl(
}
}
@Suppress("LongParameterList")
private suspend fun getGasLimit(
evmWalletManager: EthereumWalletManager,
blockchain: Blockchain,
amount: BigDecimal,
currency: Currency,
amount: Amount,
destinationAddress: String,
data: String?,
): BigInteger {
val result = if (data.isNullOrEmpty()) {
evmWalletManager.getGasLimit(
amount = createAmount(amount, currency, blockchain),
amount = amount,
destination = destinationAddress,
)
} else {
evmWalletManager.getGasLimit(
amount = createAmount(amount, currency, blockchain),
amount = amount,
destination = destinationAddress,
data = data,
)
@ -250,6 +239,18 @@ class TransactionManagerImpl(
}
}
override suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
val walletManager = getActualWalletManager(blockchain, derivationPath)
val gasPriceResult = (walletManager as? EthereumWalletManager)?.getGasPrice()
?: error("not supported for $blockchain")
val gasPrice = when (gasPriceResult) {
is Result.Failure -> error("fail to receive gasPrice")
is Result.Success -> gasPriceResult.data
}
return createMultipleProxyFees(gasPrice, gas, blockchain)
}
private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager {
val selectedUserWallet = requireNotNull(
userWalletsListManager.selectedUserWalletSync,
@ -316,27 +317,6 @@ class TransactionManagerImpl(
)
}
private fun createAmount(amount: BigDecimal, currency: Currency, blockchain: Blockchain): Amount {
return when (currency) {
is Currency.NativeToken -> {
Amount(value = amount, blockchain = blockchain)
}
is Currency.NonNativeToken -> {
Amount(convertNonNativeToken(currency), amount)
}
}
}
private fun convertNonNativeToken(token: Currency.NonNativeToken): Token {
return Token(
name = token.name,
symbol = token.symbol,
contractAddress = token.contractAddress,
decimals = token.decimalCount,
id = token.id,
)
}
private fun convertToProxyAmount(amount: Amount): ProxyAmount {
return ProxyAmount(
currencySymbol = amount.currencySymbol,

View file

@ -1,5 +0,0 @@
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000000" android:fillType="evenOdd" android:pathData="M21,11V16H7V11H21ZM21,8V6H7V8H21ZM22.41,4.59C22.04,4.21 21.53,4 21,4H7C5.89,4 5,4.89 5,6V16C5,17.1 5.89,18 7,18H21C21.53,18 22.04,17.79 22.41,17.41C22.79,17.04 23,16.53 23,16V6C23,5.47 22.79,4.96 22.41,4.59ZM18,20H3V9H1V20C1,20.53 1.21,21.04 1.59,21.41C1.96,21.79 2.47,22 3,22H18V20Z"/>
</vector>

View file

@ -35,14 +35,7 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class Disclaimer(
val isTosAccepted: Boolean,
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}"), RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
companion object {
const val IS_TOS_ACCEPTED_KEY = "isTosAccepted"
}
}
) : AppRoute(path = "/disclaimer${if (isTosAccepted) "/tos_accepted" else ""}")
@Serializable
data object OnboardingNote : AppRoute(path = "/onboarding/note")
@ -177,15 +170,9 @@ sealed class AppRoute(val path: String) : Route {
}
@Serializable
data class AccessCodeRecovery(
val userWalletId: UserWalletId,
) : AppRoute(path = "/access_code_recovery/${userWalletId.stringValue}"), RouteBundleParams {
data object AccessCodeRecovery : AppRoute(path = "/access_code_recovery"), RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
companion object {
const val USER_WALLET_ID_KEY = "userWalletId"
}
}
@Serializable

View file

@ -15,6 +15,7 @@ dependencies {
/** Domain */
implementation(projects.domain.analytics)
implementation(projects.domain.models)
/** Other */
implementation(deps.kotlin.coroutines)

View file

@ -0,0 +1,17 @@
package com.tangem.core.analytics.utils
import com.tangem.domain.models.scan.ScanResponse
/**
[REDACTED_AUTHOR]
*/
interface AnalyticsContextProxy {
fun setContext(scanResponse: ScanResponse)
fun eraseContext()
fun addContext(scanResponse: ScanResponse)
fun removeContext()
}

View file

@ -1,7 +1,6 @@
package com.tangem.datasource.api.express.models.response
import com.squareup.moshi.Json
import java.math.BigDecimal
data class ExchangeDataResponseWithTxDetails(
val dataResponse: ExchangeDataResponse,
@ -50,7 +49,10 @@ data class TxDetails(
val txData: String?, // transaction data if DEX, null if CEX
@Json(name = "txValue")
val txValue: BigDecimal, // amount (same as fromAmount)
val txValue: String, // amount (same as fromAmount for Coin, but for bridge equal to otherNativeFee)
@Json(name = "otherNativeFee")
val otherNativeFee: String?,
@Json(name = "externalTxId")
val externalTxId: String?, // null if DEX, provider transaction id if CEX
@ -63,6 +65,9 @@ data class TxDetails(
@Json(name = "txExtraId")
val txExtraId: String?,
@Json(name = "gas")
val gas: String?,
)
enum class TxType {

View file

@ -7,17 +7,23 @@ data class ExchangeStatusResponse(
@Json(name = "providerId")
val providerId: String,
@Json(name = "externalTxId")
val externalTxId: String,
@Json(name = "status")
val status: ExchangeStatus,
@Json(name = "externalTxId")
val externalTxId: String?,
@Json(name = "externalTxUrl")
val externalTxUrl: String,
val externalTxUrl: String?,
@Json(name = "error")
val error: ExchangeStatusError?,
@Json(name = "refundNetwork")
val refundNetwork: String? = null,
@Json(name = "refundContractAddress")
val refundContractAddress: String? = null,
)
enum class ExchangeStatus {

View file

@ -18,6 +18,7 @@ interface TangemTechApi {
@Query("contractAddress") contractAddress: String? = null,
@Query("exchangeable") exchangeable: Boolean? = null,
@Query("networkIds") networkIds: String? = null,
@Query("networkId") networkId: String? = null,
@Query("active") active: Boolean? = null,
@Query("searchText") searchText: String? = null,
@Query("offset") offset: Int? = null,

View file

@ -95,8 +95,6 @@ object PreferencesKeys {
booleanPreferencesKey(name = "isTokenSwapPromoOkxShown")
}
fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region")
// region Permission
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")

View file

@ -10,9 +10,12 @@ interface SwapTransactionStatusStore {
}
enum class ExchangeAnalyticsStatus(val value: String) {
WaitingTxHash("Waiting tx hash"),
InProgress("In Progress"),
Done("Done"),
Fail("Fail"),
FailTx("Fail tx"),
Unknown("Unknown"),
KYC("KYC"),
Refunded("Refunded"),
Cancelled("Canceled"),

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="add_custom_token_choose_network">Netzwerk wählen</string>
<string name="add_custom_token_title">Benutzerdefiniertes Token hinzufügen</string>
<string name="add_custom_token_title">Benutzerdef. Token hinzuf.</string>
<string name="add_tokens_title">Token verwalten</string>
<string name="address_qr_code_message_format">Sende nur %1$s ( %2$s ) vom %3$s -Netzwerk an diese Adresse. Die Verwendung anderer Token und Netzwerke kann zum Verlust von Geldern führen.</string>
<string name="alert_button_how_to_scan">So scannt man</string>
@ -23,7 +23,7 @@
<string name="app_settings_off_saved_wallet_alert_message">Durch das Entfernen der gespeicherten Karte werden alle gespeicherten Wallets und deren Zugangscodes aus der App gelöscht.</string>
<string name="app_settings_saved_access_codes">Zugangscode speichern</string>
<string name="app_settings_saved_access_codes_footer">Bei Interaktionen mit deiner Karte wird anstelle des Zugangscodes eine biometrische Authentifizierung abgefragt.</string>
<string name="app_settings_saved_wallet">Behalten die Wallet in der App</string>
<string name="app_settings_saved_wallet">Behalte die Wallet in der App</string>
<string name="app_settings_saved_wallet_footer">Aktiviere die Verknüpfung aller Wallets mit der Tangem-App. Die biometrische Authentifizierung ist zum Entsperren der App erforderlich. Das Signieren von Transaktionen erfordert das Antippen deiner Tangem-Karte.</string>
<string name="app_settings_theme_mode_dark">Dunkel</string>
<string name="app_settings_theme_mode_light">Hell</string>
@ -46,7 +46,7 @@
</plurals>
<string name="card_settings_access_code_recovery_disabled_description">Deaktiviere diese Option, wenn du nicht möchtest, dass diese Karte zum Zurücksetzen von Zugangscodes auf anderen Karten in dieser Wallets verwendet wird. Bitte beachte, dass du dann auch den Zugangscode auf dieser Karte nicht zurücksetzen kannst.</string>
<string name="card_settings_access_code_recovery_enabled_description">Ermöglicht die Verwendung dieser Karte zum Zurücksetzen des Zugangscodes auf anderen Karten in dieser Brieftasche</string>
<string name="card_settings_access_code_recovery_title">Wiederherstellung des Zugangscodes</string>
<string name="card_settings_access_code_recovery_title">Zugangscodes wiederherstellen</string>
<string name="card_settings_action_sheet_reset">Zurücksetzen</string>
<string name="card_settings_action_sheet_title">Möchtest du das wirklich tun?</string>
<string name="card_settings_change_access_code">Zugangscode ändern</string>
@ -84,14 +84,14 @@
<string name="common_cancel">Abbrechen</string>
<string name="common_claim_rewards">Stakingbelohnungen beanstpruchen</string>
<string name="common_close">Schließen</string>
<string name="common_continue">Weitermachen</string>
<string name="common_continue">Weiter</string>
<string name="common_copy">Kopieren</string>
<string name="common_copy_address">Adresse kopieren</string>
<string name="common_create">Erstellen</string>
<string name="common_custom">Benutzerdefiniert</string>
<string name="common_custom">Benutzerdef.</string>
<plurals name="common_days">
<item quantity="one">Tag</item>
<item quantity="other">Tage</item>
<item quantity="one">%d tag</item>
<item quantity="other">%d tage</item>
</plurals>
<string name="common_delete">Entfernen</string>
<string name="common_disabled">Deaktiviert</string>
@ -107,9 +107,10 @@
<string name="common_fee_selector_option_fast">Schnell</string>
<string name="common_fee_selector_option_market">Markt</string>
<string name="common_fee_selector_option_slow">Langsam</string>
<string name="common_fee_selector_title">Geschwindigkeit und Gebühr</string>
<string name="common_fee_selector_title">Gebühren</string>
<string name="common_generate_addresses">Adressen abrufen</string>
<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>
<string name="common_later">Später</string>
<string name="common_locked">Gesperrt</string>
@ -153,7 +154,7 @@
<string name="common_today">Heute</string>
<string name="common_transaction_failed">Transaktion fehlgeschlagen</string>
<string name="common_transactions">Transaktionen</string>
<string name="common_transfer">Überweisen</string>
<string name="common_transfer">Überweisung</string>
<string name="common_understand">Ich verstehe</string>
<string name="common_unknown_error">Es ist ein Fehler aufgetreten. Bitte versuche es erneut.</string>
<string name="common_unreachable">Nicht erreichbar</string>
@ -225,6 +226,8 @@
<string name="express_exchange_by">Getauscht von %s</string>
<string name="express_exchange_notification_failed_text">Besuche die Website des Anbieters, um dein Geld zurückzuerhalten</string>
<string name="express_exchange_notification_failed_title">Fehler beim Vorgang durch Anbieter</string>
<string name="express_exchange_notification_refund_text">Der Transaktionsbetrag wurde aufgrund von OKX- oder Bridge-Regeln in %1$s auf deine Wallet zurückerstattet. %2$s</string>
<string name="express_exchange_notification_refund_title">Der Betrag wurde in %1$s (%2$s Netzwerk) zurückerstattet.</string>
<string name="express_exchange_notification_verification_text">Besuche die Website des Anbieters zur Überprüfung</string>
<string name="express_exchange_notification_verification_title">KYC-Überprüfung durch den Anbieter erforderlich</string>
<string name="express_exchange_status_canceled">Abgebrochen</string>
@ -426,7 +429,7 @@
<string name="onboarding_button_backup_now">Jetzt sichern</string>
<string name="onboarding_button_backup_origin">Scannen der Hauptkarte</string>
<string name="onboarding_button_continue_wallet">Weiter zu meiner Wallet</string>
<string name="onboarding_button_finalize_backup">Abschließen der Sicherung</string>
<string name="onboarding_button_finalize_backup">Backup abschließen</string>
<string name="onboarding_button_receive_crypto">Krypto empfangen</string>
<string name="onboarding_button_scan_origin_card">Primärkarte scannen</string>
<string name="onboarding_button_skip_backup">Für später überspringen</string>
@ -436,7 +439,7 @@
<string name="onboarding_create_wallet_header">Erstelle eine Wallet</string>
<string name="onboarding_create_wallet_options_button_options">Andere Optionen</string>
<string name="onboarding_create_wallet_options_message">Deine Schlüssel(private-keys) werden sicher im Inneren der Karte generiert. Es gibt keine Seed-Phrase, d. h. niemand kann sie exportieren oder stehlen.</string>
<string name="onboarding_create_wallet_options_title">Schlüssel privat generieren</string>
<string name="onboarding_create_wallet_options_title">Schlüssel anonym generieren</string>
<string name="onboarding_done_body">Deine Karte ist aktiviert und einsatzbereit</string>
<string name="onboarding_done_header">Erfolgreich!</string>
<string name="onboarding_exit_alert_message">In diesem Fall musst du ganz von vorne anfangen.</string>
@ -454,22 +457,22 @@
<item quantity="one">leer</item>
<item quantity="other">%d Wörter</item>
</plurals>
<string name="onboarding_seed_import_message">Um deine Wallets zu importieren, gib bitte deineSeed-Phrase in das folgende Feld ein</string>
<string name="onboarding_seed_import_message">Um deine Wallets zu importieren, gib bitte deine Seed-Phrase in das folgende Feld ein</string>
<string name="onboarding_seed_intro_button_generate">Seed-Phrase generieren</string>
<string name="onboarding_seed_intro_button_import">Wallet importieren</string>
<string name="onboarding_seed_intro_message">Eine Seed-Phrase ist eine Reihe von Wörtern, mit denen du deine Wallet wiederherstellen kannst. Im Gegensatz zu den von der Karte generierten Schlüsseln sind Seed-Phrasen ungeschützt und können kopiert und gestohlen werden. Die Verwendung dieser Option erfolgt auf eigene Gefahr.</string>
<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">Altbestand</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">Also, lass uns das überprüfen</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>
<string name="onboarding_subtitle_one_backup_card">Du kannst eine weitere Karte hinzufügen oder den Sicherungsvorgang abschließen</string>
<string name="onboarding_subtitle_scan_backup_card_format">Bereite die Sicherungskarte mit der Nummer %s vor.</string>
<string name="onboarding_subtitle_scan_primary">Scanne die primär-Karte, um den Sicherungsvorgang zu starten.</string>
<string name="onboarding_subtitle_scan_primary_card_format">Bereite die primäre Karte mit der Nummer %s vor.</string>
<string name="onboarding_subtitle_success_tangem_wallet_onboarding">Deine Wallet-Karte ist konfiguriert und einsatzbereit.</string>
<string name="onboarding_subtitle_success_tangem_wallet_onboarding">Deine Tangem-Karte ist konfiguriert und einsatzbereit.</string>
<string name="onboarding_subtitle_two_backup_cards">Maximale Anzahl an Karten hinzugefügt. Schließe den Sicherungsvorgang ab.</string>
<string name="onboarding_title">Karte aktivieren</string>
<string name="onboarding_title_backup_card_format">Sicherungskarte Nr. %d</string>
@ -496,7 +499,7 @@
<string name="organize_tokens_group">Gruppe erstellen</string>
<string name="organize_tokens_sort_by_balance">Nach Guthaben</string>
<string name="organize_tokens_title">Token organisieren</string>
<string name="organize_tokens_ungroup">Gruppierung aufheben</string>
<string name="organize_tokens_ungroup">Gruppierung aufh.</string>
<string name="qr_scanner_camera_denied_gallery_button">Wählen aus der Galerie aus</string>
<string name="qr_scanner_camera_denied_settings_button">Einstellungen</string>
<string name="qr_scanner_camera_denied_text">Du hast keinen Zugriff auf deine Kamera gewährt</string>
@ -626,7 +629,7 @@
<string name="send_summary_tap_hint">Berühre an beliebiger Stelle für Änderungen</string>
<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_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>
@ -644,9 +647,10 @@
<string name="staking_active">Aktiv</string>
<string name="staking_active_footer">Um deine Kryptos zu unstaken, klick hier.</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>
<string name="staking_details_annual_percentage_rate_info">Die jährliche prozentuale Rendite, die du durch die Teilnahme am Staking erzielen kannst.</string>
<string name="staking_details_apr">Effektiver Jahreszins</string>
<string name="staking_details_apy">Jährliche prozentuale Rendite</string>
<string name="staking_details_apy_info">Die jährliche prozentuale Rendite, die du durch die Teilnahme am Staking erzielen kannst.</string>
<string name="staking_details_available">Verfügbar</string>
<string name="staking_details_average_reward_rate">Durchschnittliche Belohnungsquote</string>
<string name="staking_details_estimated_profit">%s geschätzter Profit</string>
@ -654,7 +658,6 @@
<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_on_stake">Gestaked</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>
@ -665,14 +668,28 @@
<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>
<string name="staking_details_warmup_period">Aufwärmphase</string>
<string name="staking_details_warmup_period_info">Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking.</string>
<string name="staking_initial_info_title">Stake %s</string>
<string name="staking_migrate">Migrieren</string>
<string name="staking_native">Natives Staking</string>
<string name="staking_notification_earn_rewards_text" formatted="false">Mit Staking kannst du %1s verdienen. Deine Staking-Belohnungen kommen alle ~%2s Tage.</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_rebond">Erneut binden</string>
<string name="staking_restake">Erneut staken</string>
<string name="staking_restake_rewards">Belohnungen erneut staken</string>
<string name="staking_revoke">Widerrufen</string>
<string name="staking_revote">Neuwahl</string>
<string name="staking_rewards">Belohnungen</string>
<string name="staking_stake_locked">Stake gesperrt</string>
<string name="staking_stake_more">Mehr staken</string>
<string name="staking_unstaked">unstaken</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>
<string name="staking_unstaking">Staking beenden</string>
<string name="staking_validator">Validator/ Prüfer</string>
<string name="staking_vote">Abstimmung</string>
<string name="staking_vote_locked">Abstimmung gesperrt</string>
<string name="staking_withdraw">Zurückziehen</string>
<string name="story_awe_description">Bewahre deine Krypto-Assets sicher auf, während die privaten Schlüssel auf deiner Karte bleiben</string>
<string name="story_awe_title">Revolutionäre Hardware-Wallet</string>
<string name="story_backup_description">Bis zu 3 physische Karten pro Wallet</string>
@ -748,8 +765,8 @@
<string name="twins_recreate_warning">Diese Aktion ist unumkehrbar. Du hast keinen Zugriff mehr auf die alte Wallet.</string>
<string name="twins_scan_twin_with_number">Tippe auf die Doppelkarte mit der Nummer %s und entferne sie erst am Ende des Vorgangs.</string>
<string name="unlock_wallet_description_full">Verwende %s oder scanne eine Karte, um Zugriff auf deine Wallet zu erhalten.</string>
<string name="user_push_notification_agreement_argument_one">Bleib auf dem Laufenden mit den neuesten Funktionen und Neuigkeiten</string>
<string name="user_push_notification_agreement_argument_two">Sei der Erste, der von neuen Aktionen erfährt</string>
<string name="user_push_notification_agreement_argument_one">Bleib auf dem Laufenden mit den neuesten Funktionen und Neuigkeiten</string>
<string name="user_push_notification_agreement_argument_two">Sei der Erste, der von neuen Aktionen erfährt</string>
<string name="user_push_notification_agreement_header">Möchtest du Push-Benachrichtigungen verwenden?</string>
<string name="user_wallet_list_add_button">Neues Wallet hinzufügen</string>
<string name="user_wallet_list_delete_prompt">Möchtest du diese Wallet wirklich löschen?</string>
@ -805,9 +822,9 @@
<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>
<string name="warning_beacon_chain_retirement_title">BNB Beacon Chain wird abgeschaltet</string>
<string name="warning_button_could_be_better">Könnte besser sein</string>
<string name="warning_button_could_be_better">verbesserungswürdig</string>
<string name="warning_button_like_it">Gefällt mir</string>
<string name="warning_button_ok">OK, ich hab\'s!</string>
<string name="warning_button_ok">OK, habe ich verstanden!</string>
<string name="warning_button_really_cool">Echt toll!</string>
<string name="warning_button_refresh">Aktualisieren</string>
<string name="warning_demo_mode_message">Du befindest sich derzeit im Demo-Modus</string>

View file

@ -87,6 +87,10 @@
<string name="common_copy_address">Copier l\'adresse</string>
<string name="common_create">Créer</string>
<string name="common_custom">Personnalisé</string>
<plurals name="common_days">
<item quantity="one">%d jour</item>
<item quantity="other">%d jours</item>
</plurals>
<string name="common_delete">Supprimer</string>
<string name="common_disabled">Désactivé</string>
<string name="common_done">Exécuté</string>
@ -272,8 +276,8 @@
<string name="give_permission_swap_subtitle" formatted="false">Pour continuer, accordez aux smart contracts de %1s l\'autorisation d\'utiliser votre %2s</string>
<string name="give_permission_title">Donner l\'autorisation</string>
<string name="give_permission_unlimited">Illimité</string>
<string name="home_button_order">Commander une carte</string>
<string name="home_button_scan">Scannez la carte</string>
<string name="home_button_order">Commandez</string>
<string name="home_button_scan">Scannez</string>
<string name="initial_message_change_access_code_body">Touchez, pour modifier le code d\'accès</string>
<string name="initial_message_change_passcode_body">Touchez, pour modifier le mot de passe</string>
<string name="initial_message_create_wallet_body">Pour créer le portefeuille, appuyez sur la carte comme indiqué ci-dessus et ne la retirez pas jusqu\'à la fin de l\'opération</string>
@ -324,6 +328,7 @@
<string name="markets_add_to_portfolio_button">Ajouter au portfolio</string>
<string name="markets_common_my_portfolio">Mon portfolio</string>
<string name="markets_common_title">Marché</string>
<string name="markets_generate_addresses_notification">Pour générer des adresses pour les réseaux sélectionnés, vous devez scanner votre carte Tangem.</string>
<string name="markets_select_wallet">Sélectionnez un portefeuille</string>
<string name="markets_sort_by_title">Trier par</string>
<string name="markets_token_details_insights">Idées</string>
@ -371,7 +376,7 @@
<string name="onboarding_exit_alert_title">Voulez-vous quitter le processus d\'activation ?</string>
<string name="onboarding_getting_started">Initialiser</string>
<string name="onboarding_linking_error_card_with_wallets">Un autre portefeuille a déjà été créé sur la carte que vous essayez d\'ajouter. Si vous avez des fonds dans ce portefeuille, veuillez les retirer, puis réinitialiser cette carte et l\'ajouter comme sauvegarde.</string>
<string name="onboarding_navbar_title_creating_backup">Création d\'une sauvegarde</string>
<string name="onboarding_navbar_title_creating_backup">Sauvegarde en cours</string>
<string name="onboarding_seed_button_read_more">En savoir plus sur les seed phrases</string>
<plurals name="onboarding_seed_generate_message_words_count">
<item quantity="one">Empty</item>
@ -384,7 +389,7 @@
</plurals>
<string name="onboarding_seed_import_message">Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous</string>
<string name="onboarding_seed_intro_button_generate">Générer une seed phrase</string>
<string name="onboarding_seed_intro_button_import">Importer un portefeuille</string>
<string name="onboarding_seed_intro_button_import">Importez</string>
<string name="onboarding_seed_intro_message">Une seed phrase est une série de mots qui vous permet de récupérer votre portefeuille. Contrairement aux clés générées par la carte, les seed phrases ne sont pas protégées et peuvent être copiées et volées. Utilisez cette option à vos propres risques.</string>
<string name="onboarding_seed_intro_title">Utiliser une seed phrase</string>
<string name="onboarding_seed_mnemonic_invalid_checksum">Seed phrase invalide. Veuillez vérifier l\'ordre des mots.</string>
@ -559,6 +564,7 @@
<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="send_wallet_balance_format">%1$s (%2$s)</string>
<string name="sent_transaction_sent_title">Transaction envoyée</string>
@ -568,9 +574,8 @@
<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_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_apy">APY</string>
<string name="staking_details_apy_info">Le pourcentage de rendement annuel que vous pouvez gagner en participant au staking.</string>
<string name="staking_details_available">Disponible</string>
<string name="staking_details_average_reward_rate">Taux de récompense moyen</string>
<string name="staking_details_estimated_profit">%s profit estimatif</string>
@ -578,12 +583,11 @@
<string name="staking_details_metrics_block_header">Métriques</string>
<string name="staking_details_minimum_requirement">Minimum requis</string>
<string name="staking_details_no_rewards_to_claim">Aucune récompense à réclamer</string>
<string name="staking_details_on_stake">En jeu</string>
<string name="staking_details_reward_claiming">Réclamation de récompense</string>
<string name="staking_details_reward_claiming_info">Un moyen de recevoir des récompenses de staking. Il peut être réclamé automatiquement ou manuellement.</string>
<string name="staking_details_reward_schedule">Calendrier de récompenses</string>
<string name="staking_details_reward_schedule_info">Il s\'agit d\'un calendrier qui détermine le moment où les participants au staking reçoivent leurs récompenses.</string>
<string name="staking_details_rewards_to_claim">Récompenses à réclamer : %s</string>
<string name="staking_details_rewards_to_claim">Récompenses à réclamer: %s</string>
<string name="staking_details_title">Staking %s</string>
<string name="staking_details_unbonding_period">Période de détachement</string>
<string name="staking_details_unbonding_period_info">La période que vous devez attendre après avoir demandé le retrait des fonds du staking avant que les jetons ne soient disponibles.</string>
@ -641,7 +645,7 @@
<string name="token_details_hide_token">Masquer le jeton</string>
<string name="token_details_staking_block_subtitle">Le Staking vous permet d\'en gagner %1$s et d\'obtenir des récompenses tous les %2$s jours</string>
<string name="token_details_staking_block_title">Gagnez jusqu\'à %s récompense de mise par an</string>
<string name="token_details_token_type_subtitle">%1$s jeton dans %%image%% le réseau %2$s</string>
<string name="token_details_token_type_subtitle">%1$s jeton dans %%image%% %2$s le réseau</string>
<string name="token_details_token_type_subtitle_no_standard">Jeton dans le %%image%% %1$s réseau</string>
<string name="token_details_unable_hide_alert_message">Le jeton %1$s (%2$s) est la principale devise du réseau %3$s et ne peut pas être masqué tant que vous avez d\'autres jetons de ce réseau dans la liste</string>
<string name="token_details_unable_hide_alert_title">Impossible de masquer %s</string>
@ -669,6 +673,7 @@
<string name="twins_scan_twin_with_number">Appuyez sur la carte jumelle avec le numéro %s et ne la retirez pas jusqu\'à la fin de l\'opération</string>
<string name="unlock_wallet_description_full">Utilisez %s ou scannez une carte pour avoir accès à votre portefeuille</string>
<string name="user_push_notification_agreement_argument_one">Restez à jour avec les dernières fonctionnalités et actualités</string>
<string name="user_push_notification_agreement_argument_two">Soyez le premier informé des nouvelles promotions</string>
<string name="user_push_notification_agreement_header">Souhaitez-vous utiliser les notifications push?</string>
<string name="user_wallet_list_add_button">Ajouter un nouveau portefeuille</string>
<string name="user_wallet_list_delete_prompt">Êtes-vous sûr de vouloir supprimer ce portefeuille ?</string>
@ -724,7 +729,7 @@
<string name="warning_backup_errors_title">Erreur d\'activation</string>
<string name="warning_beacon_chain_retirement_content">Selon les développeurs du réseau BNB, le support de la norme BEP-2\nprendra fin en juin 2024. Pour éviter de perdre des actifs avec cette norme, veuillez les convertir à la norme BEP-20. Utilisez notre service de d\'échange pour les transférer sur le réseau BNB Smart Chain.</string>
<string name="warning_beacon_chain_retirement_title">BNB Beacon Chain va s\'arrêter de fonctionner</string>
<string name="warning_button_could_be_better">Pourrait être mieux</string>
<string name="warning_button_could_be_better">Pas terrible</string>
<string name="warning_button_like_it">J\'aime</string>
<string name="warning_button_ok">Ok, je l\'ai!</string>
<string name="warning_button_really_cool">Vraiment cool !</string>

View file

@ -14,6 +14,7 @@
<string name="common_save_changes">Mantieni le modifiche</string>
<string name="common_send">Invia</string>
<string name="common_success">Con successo</string>
<string name="common_warning">Avviso</string>
<string name="details_manage_security_access_code">Codice di accesso</string>
<string name="details_manage_security_access_code_description">Prima di scansionare la carta sarà necessario inserire il codice di accesso corretto</string>
<string name="details_manage_security_long_tap">Mantenimento della carta</string>

View file

@ -89,7 +89,7 @@
<string name="common_create">作成</string>
<string name="common_custom">設定</string>
<plurals name="common_days">
<item quantity="other"></item>
<item quantity="other">%d </item>
</plurals>
<string name="common_delete">削除</string>
<string name="common_disabled">無効</string>
@ -108,6 +108,7 @@
<string name="common_fee_selector_title">速度と料金</string>
<string name="common_generate_addresses">アドレスを取得する</string>
<string name="common_go_to_provider">プロバイダーへ移動</string>
<string name="common_go_to_token">トークンへ移動</string>
<string name="common_import">インポート</string>
<string name="common_later">後で</string>
<string name="common_locked">ロックされています</string>
@ -143,7 +144,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>
@ -223,6 +224,8 @@
<string name="express_exchange_by">%sによる交換</string>
<string name="express_exchange_notification_failed_text">返金を受けるには、プロバイダーのウェブサイトにアクセスしてください。</string>
<string name="express_exchange_notification_failed_title">プロバイダーによる操作が失敗しました。</string>
<string name="express_exchange_notification_refund_text">OKXまたはブリッジのルールにより、取引金額は%1$sでウォレットに返金されました。%2$s</string>
<string name="express_exchange_notification_refund_title">金額は %1$s%2$sネットワークで返金されました</string>
<string name="express_exchange_notification_verification_text">確認するには、プロバイダーのウェブサイトにアクセスしてください。</string>
<string name="express_exchange_notification_verification_title">プロバイダーによる本人確認手続きが必要です。</string>
<string name="express_exchange_status_canceled">キャンセルされました</string>
@ -335,7 +338,7 @@
<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_generate_addresses_notification">選択したネットワークのアドレスを生成するには、Tangemカードをスキャンする必要があります。</string>
<string name="markets_loading_error_title">データを読み込めません…</string>
<string name="markets_quick_actions">クイックアクション</string>
<string name="markets_search_result_title">結果</string>
@ -446,7 +449,7 @@
</plurals>
<string name="onboarding_seed_import_message">ウォレットをインポートするには、下のフィールドにシードフレーズを入力してください。</string>
<string name="onboarding_seed_intro_button_generate">シードフレーズを生成する</string>
<string name="onboarding_seed_intro_button_import">ウォレットをインポートする</string>
<string name="onboarding_seed_intro_button_import">ウォレットをインポート</string>
<string name="onboarding_seed_intro_message">シードフレーズは、ウォレットを復元できる一連の単語です。カードによって生成される秘密鍵とは異なり、シードフレーズは保護されていないため、コピーされて盗まれる可能性があります。このオプションは自己責任で使用してください。</string>
<string name="onboarding_seed_intro_title">シードフレーズを使用する</string>
<string name="onboarding_seed_mnemonic_invalid_checksum">無効なシードフレーズです。語順を確認してください。</string>
@ -613,7 +616,7 @@
<string name="send_sending">送金中...</string>
<string name="send_summary_tap_hint">変更するには任意の箇所をタップしてください</string>
<string name="send_summary_title">%sを送金する</string>
<string name="send_summary_transaction_description">** %1$s ** を送金する (ネットワーク手数料%2$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>
@ -632,17 +635,18 @@
<string name="staking_active">アクティブ</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">APY</string>
<string name="staking_details_annual_percentage_rate_info">ステーキングに参加することで得られる年間収益率。</string>
<string name="staking_details_apr">APR</string>
<string name="staking_details_apy">APY</string>
<string name="staking_details_apy_info">ステーキングに参加することで得られる年間収益率。</string>
<string name="staking_details_available">利用可能</string>
<string name="staking_details_average_reward_rate">平均報酬率</string>
<string name="staking_details_banner_text">ステーキングとは?</string>
<string name="staking_details_estimated_profit">%s 推定利益</string>
<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_on_stake">ステーキング中</string>
<string name="staking_details_reward_claiming">請求中の報酬</string>
<string name="staking_details_reward_claiming_info">ステーキング報酬を受け取る方法。自動または手動で請求できます。</string>
<string name="staking_details_reward_schedule">報酬スケジュール</string>
@ -653,14 +657,28 @@
<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_initial_info_title">%sをステーキングする</string>
<string name="staking_migrate">移行</string>
<string name="staking_native">ネイティブステーキング</string>
<string name="staking_notification_earn_rewards_text" formatted="false">ステーキングにより%1sを獲得できます。ステーキング報酬は ~ %2s日ごとに届きます。</string>
<string name="staking_notification_earn_rewards_title">ステーキング報酬を獲得</string>
<string name="staking_notification_unstake_text">ステーキング解除後、報酬の獲得はすぐに停止します。ステーキング解除プロセスには%sかかります。</string>
<string name="staking_rebond">再結束</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_rewards">報酬</string>
<string name="staking_stake_locked">ステーキングはロックされています</string>
<string name="staking_stake_more">もっとステーキングする</string>
<string name="staking_unlocked_locked">ステーキング解除はロックされています</string>
<string name="staking_unstaked">スタックされていない</string>
<string name="staking_unstaked_footer">資産を請求するために、unstakedを確認してください</string>
<string name="staking_unstaking">ステーキング解除</string>
<string name="staking_validator">バリデーター</string>
<string name="staking_vote">投票する</string>
<string name="staking_vote_locked">投票はロックされています</string>
<string name="staking_withdraw">引き出す</string>
<string name="story_awe_description">カード内に秘密鍵を保管しながら暗号資産を安全に保管します</string>
<string name="story_awe_title">革新的なハードウェアウォレット</string>
<string name="story_backup_description">1つのウォレットに最大3枚のカード</string>

View file

@ -81,6 +81,7 @@
<string name="common_buy_currency">Перейти на %1$s</string>
<string name="common_camera_denied_alert_message">Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности.</string>
<string name="common_cancel">Отмена</string>
<string name="common_claim_rewards">Вывести награду</string>
<string name="common_close">Закрыть</string>
<string name="common_continue">Продолжить</string>
<string name="common_copy">Копировать</string>
@ -110,6 +111,7 @@
<string name="common_fee_selector_title">Скорость и комиссия</string>
<string name="common_generate_addresses">Получить адреса</string>
<string name="common_go_to_provider">К провайдеру</string>
<string name="common_go_to_token">Перейти в токен</string>
<string name="common_import">Импортировать</string>
<string name="common_later">Позже</string>
<string name="common_locked">Заблокирован</string>
@ -142,6 +144,7 @@
<string name="common_share">Поделиться</string>
<string name="common_sign">Подписать</string>
<string name="common_sign_and_send">Подписать и отправить</string>
<string name="common_stake">Застейкать</string>
<string name="common_staking">Стейкинг</string>
<string name="common_start">Начать</string>
<string name="common_submit">Отправить</string>
@ -156,6 +159,7 @@
<string name="common_understand">Я понял</string>
<string name="common_unknown_error">Произошла ошибка. Пожалуйста, попробуйте снова.</string>
<string name="common_unreachable">Недоступно</string>
<string name="common_unstake">Завершить стейкинг</string>
<string name="common_yes">Да</string>
<string name="contract_address_copied_message">Адрес контракта скопирован!</string>
<string name="currency_subtitle_expanded">Доступные сети</string>
@ -223,6 +227,8 @@
<string name="express_exchange_by">Обмен через %s</string>
<string name="express_exchange_notification_failed_text">Чтобы вернуть ваши деньги, посетите сайт провайдера</string>
<string name="express_exchange_notification_failed_title">Операция не выполнена провайдером</string>
<string name="express_exchange_notification_refund_text">Отправленные средства были возвращены в %1$s на ваш кошелек в соответствии с правилами OKX или моста обмена. %2$s</string>
<string name="express_exchange_notification_refund_title">Сумма была возвращена в %1$s (%2$s сети)</string>
<string name="express_exchange_notification_verification_text">Посетите сайт провайдера для проверки</string>
<string name="express_exchange_notification_verification_title">Провайдер запрашивает прохождение верификации</string>
<string name="express_exchange_status_canceled">Отменен</string>
@ -277,6 +283,7 @@
<string name="give_permission_policy_type_footer">Укажите лимит доступа к выбранному токену</string>
<string name="give_permission_rows_amount">Количество %s</string>
<string name="give_permission_staking_footer">Функция подтверждения необходима для предоставления другому адресу разрешения на использование определенного количества ваших токенов.По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту StakeKit разрешение использовать ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете осуществить стейкинг токена.</string>
<string name="give_permission_staking_subtitle">Чтобы продолжить, вам необходимо разрешить смарт контракту StakeKit использовать ваш %s</string>
<string name="give_permission_swap_subtitle" formatted="false">Чтобы продолжить, вам нужно разрешить смарт-контракту %1s использовать ваш %2s</string>
<string name="give_permission_title">Дать разрешение</string>
<string name="give_permission_unlimited">Безлимитно</string>
@ -327,6 +334,7 @@
<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_generate_addresses_notification">Чтобы создать адреса для выбранных сетей, необходимо отсканировать свой кошелек Tangem.</string>
<string name="markets_token_details_links">Ссылки</string>
<string name="markets_token_details_metrics">Метрики</string>
<string name="onboarding_access_code_feature_1_description">Вам необходимо установить единый код доступа для защиты всех ваших карт</string>
@ -572,20 +580,49 @@
<string name="settings_forget_wallet">Забыть кошелек</string>
<string name="settings_forget_wallet_footer">Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова.</string>
<string name="settings_wallet_name_title">Имя</string>
<string name="staking_amount_requirement_error">Сумма для стейкинда должна быть не менее %s</string>
<string name="staking_details_apy">APY</string>
<string name="staking_details_apy_info">Годовой процентный доход, который вы можете получить от участия в стейкинге.</string>
<string name="staking_active">Активно</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">APY</string>
<string name="staking_details_annual_percentage_rate_info">Годовой процентный доход, который вы можете получить от участия в стейкинге.</string>
<string name="staking_details_apr">APR</string>
<string name="staking_details_available">Доступно</string>
<string name="staking_details_estimated_profit">%s</string>
<string name="staking_details_average_reward_rate">Средння ставка вознаграждения</string>
<string name="staking_details_estimated_profit">%s оценка доходности</string>
<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_reward_claiming">Способ возраграждения</string>
<string name="staking_details_reward_claiming_info">Способ получения вознаграждений за стейкинг. Он может быть автоматическим или ручным.</string>
<string name="staking_details_reward_claiming_info">Способ получения вознаграждений за стейкинг. Он может быть автоматическим, при котором вознаграждение само зачисляется вам на адрес или в ручную, когда вознаграждение нужно вывести, создав транзакцию на её получение.</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_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_details_warmup_period_info">Время, необходимое для начала процесса стейкинга и активации процесса начисления наград</string>
<string name="staking_initial_info_title">Застейкать %s</string>
<string name="staking_migrate">Переместить</string>
<string name="staking_native">Нативный стейкинг</string>
<string name="staking_notification_earn_rewards_text" formatted="false">Стейкинг дает возможность вам получать %1s. Награда будет зачисляться каждый %2s</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_restake_rewards">Застейкать вознаграждения</string>
<string name="staking_revoke">Отозвать</string>
<string name="staking_revote">Переголосовать</string>
<string name="staking_rewards">Вознаграждения</string>
<string name="staking_stake_more">Застейкать еще</string>
<string name="staking_unlocked_locked">Разблокировать</string>
<string name="staking_unstaked">Выведено из стейкинга</string>
<string name="staking_unstaked_footer">Проверьте процесс завершения стейкинга, чтобы вывести свои средства.</string>
<string name="staking_unstaking">Завершение стейкинга</string>
<string name="staking_validator">Валидатор</string>
<string name="staking_vote">Проголосовать</string>
<string name="staking_withdraw">Вывод</string>
<string name="story_awe_description">Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте.</string>
<string name="story_awe_title">Революционный аппаратный кошелек</string>
<string name="story_backup_description">До трех карт с одним кошельком</string>

View file

@ -114,6 +114,7 @@
<string name="common_fee_selector_title">Швидкість та комісія</string>
<string name="common_generate_addresses">Отримати адреси</string>
<string name="common_go_to_provider">Перейти до провайдера</string>
<string name="common_go_to_token">Перейти до токену</string>
<string name="common_import">Імпортувати</string>
<string name="common_later">Пізніше</string>
<string name="common_locked">Заблокований</string>
@ -229,6 +230,8 @@
<string name="express_exchange_by">Обмін через %s</string>
<string name="express_exchange_notification_failed_text">Щоб повернути ваші кошти, відвідайте сайт провайдера</string>
<string name="express_exchange_notification_failed_title">Операція не виконана провайдером</string>
<string name="express_exchange_notification_refund_text">Сума транзакції була повернута в %1$s на ваш гаманець відповідно до правил OKX або мосту обміну. %2$s</string>
<string name="express_exchange_notification_refund_title">Сума була повернута в %1$s (%2$s мережі)</string>
<string name="express_exchange_notification_verification_text">Відвідайте сайт провайдера для перевірки</string>
<string name="express_exchange_notification_verification_title">Провайдер вимагає проходження KYC верифікації</string>
<string name="express_exchange_status_canceled">Скасовано</string>
@ -344,7 +347,7 @@
<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_generate_addresses_notification">Щоб згенерувати адреси для обраних мереж, потрібно відсканувати свою картку Tangem.</string>
<string name="markets_loading_error_title">Не вдалося завантажити дані...</string>
<string name="markets_quick_actions">Швидкі дії</string>
<string name="markets_search_result_title">Результат</string>
@ -464,7 +467,7 @@
</plurals>
<string name="onboarding_seed_import_message">Щоб імпортувати гаманець, введіть seed-фразу в поле нижче</string>
<string name="onboarding_seed_intro_button_generate">Згенерувати seed-фразу</string>
<string name="onboarding_seed_intro_button_import">Імпортувати гаманець</string>
<string name="onboarding_seed_intro_button_import">Імпорт гаманця</string>
<string name="onboarding_seed_intro_message">Seed-фраза — це набір слів, який дозволяє відновити ваш гаманець. На відміну від ключів, що генеруються карткою, seed-фраза не захищена і може бути скопійована та викрадена. Використовуйте цю опцію на свій власний ризик.</string>
<string name="onboarding_seed_intro_title">Використовувати seed-фразу</string>
<string name="onboarding_seed_mnemonic_invalid_checksum">Невірна seed-фраза. Будь ласка, перевірте порядок слів.</string>
@ -637,15 +640,15 @@
<string name="send_sending">Надсилання...</string>
<string name="send_summary_tap_hint">Торкніться будь-якого поля, щоб змінити його</string>
<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_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_tron_account_activation_error">%1$s — це монета у мережі Tron. Щоб розрахувати комісію та здійснити транзакцію, вам необхідно внести певну кількість Tron (TRX) на свій рахунок.</string>
<string name="send_validation_invalid_address">Недійсна адреса</string>
<string name="send_wallet_balance_format">%1$s (%2$s)</string>
<string name="sent_transaction_sent_title">Трансакцію надіслано</string>
@ -656,9 +659,8 @@
<string name="staking_active">Активний</string>
<string name="staking_active_footer">Щоб вивести активи зі стейкінгу, натисніть тут.</string>
<string name="staking_amount_requirement_error">Сума для стейкінгу має бути не менше %s</string>
<string name="staking_details_annual_percentage_rate_info">Річний відсоток, який ви можете отримати, беручи участь у стейкінгу.</string>
<string name="staking_details_apr">APR</string>
<string name="staking_details_apy">APY</string>
<string name="staking_details_apy_info">Річний відсоток, який ви можете отримати, беручи участь у стейкінгу.</string>
<string name="staking_details_available">Доступно</string>
<string name="staking_details_average_reward_rate">Середня ставка винагороди</string>
<string name="staking_details_estimated_profit">~ прибуток за %s</string>
@ -666,7 +668,6 @@
<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_on_stake">В стейкінгу</string>
<string name="staking_details_reward_claiming">Отримати винагороду</string>
<string name="staking_details_reward_claiming_info">Спосіб отримання винагороди за стейкінг. Його можна отримати автоматично або вручну.</string>
<string name="staking_details_reward_schedule">Розклад винагород</string>
@ -677,6 +678,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_initial_info_title">Стейкінг %s</string>
<string name="staking_native">Нативний стейкінг</string>
<string name="staking_notification_earn_rewards_text" formatted="false">Стейкінг дозволяє заробляти %1s. Ваші винагороди за стейкінг надходять кожні ~%2s днів.</string>
<string name="staking_notification_earn_rewards_title">Отримуйте винагороду за стейкінг</string>
@ -817,7 +819,7 @@
<string name="warning_backup_errors_title">Помилка активації</string>
<string name="warning_beacon_chain_retirement_content">За рішенням розробників мережі BNB стандарт BEP-2 перестане підтримуватись у червні 2024 року. Щоб не втратити свої активи, їх необхідно конвертувати у стандарт BEP-20. Використовуйте функцію обміну, щоб перевести їх у мережу BNB Smart Chain.</string>
<string name="warning_beacon_chain_retirement_title">Відключення мережі BNB Beacon Chain</string>
<string name="warning_button_could_be_better">Могло б бути краще</string>
<string name="warning_button_could_be_better">Можна краще</string>
<string name="warning_button_like_it">Вподобати</string>
<string name="warning_button_ok">Зрозуміло!</string>
<string name="warning_button_really_cool">Дуже круто!</string>

View file

@ -9,6 +9,9 @@
<string name="alert_failed_to_send_transaction_message">原因:%s</string>
<string name="alert_failed_to_send_transaction_title">無法發送交易</string>
<string name="alert_manage_tokens_unsupported_message">此卡不支持%1$s網路上的代幣因為韌體限制</string>
<string name="alert_negative_app_rate_sent_message">感謝您的反饋。我們會盡快回复</string>
<string name="alert_negative_app_rate_sent_title">你的建議已送出</string>
<string name="alert_troubleshooting_scan_card_message">請嘗試完全按照動畫中顯示的方式點擊卡片或請求支持</string>
<string name="alert_troubleshooting_scan_card_title">有困難在掃描卡上嗎?</string>
<string name="alert_unsupported_card">此卡不適用於此app</string>
<string name="app_settings_enable_biometrics_description">轉到設置以在 Tangem App 中啟用生物識別身份驗證</string>
@ -53,6 +56,7 @@
<string name="common_create">創造</string>
<string name="common_delete">刪除</string>
<string name="common_disabled">禁用</string>
<string name="common_disconnect">斷開連接</string>
<string name="common_done">完成</string>
<string name="common_enable">允許</string>
<string name="common_enabled">啟用</string>
@ -65,6 +69,7 @@
<string name="common_origin_card">主卡片</string>
<string name="common_reject">拒絕</string>
<string name="common_rename">重新命名</string>
<string name="common_retry">重試</string>
<string name="common_save_changes">保存設置</string>
<string name="common_search">搜索</string>
<string name="common_search_tokens">搜尋代幣</string>
@ -83,6 +88,7 @@
<string name="common_transactions">交易</string>
<string name="common_understand">我了解</string>
<string name="common_unreachable">無法觸達</string>
<string name="common_warning">警告</string>
<string name="common_yes"></string>
<string name="contract_address_copied_message">已複製代幣地址</string>
<string name="currency_subtitle_expanded">支持的網路</string>
@ -115,6 +121,7 @@
<string name="details_row_title_currency">App Currency</string>
<string name="details_row_title_issuer">發行人</string>
<string name="details_row_title_signed_hashes">簽署</string>
<string name="details_security_management_warning">如果您忘記密碼,您將無法使用您的資金。無法恢復代碼</string>
<string name="details_title">更多</string>
<string name="disclaimer_error_loading">檢查您的網路連接或切換到其他網絡</string>
<string name="disclaimer_title">服務條款</string>

View file

@ -4,21 +4,15 @@
<string name="add_custom_token_title">Add custom token</string>
<string name="add_tokens_title">Manage tokens</string>
<string name="address_qr_code_message_format">Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds.</string>
<string name="alert_app_feedback_sent_message">Thank you for your feedback</string>
<string name="alert_app_feedback_sent_title">Sent successfully</string>
<string name="alert_button_how_to_scan">How to scan</string>
<string name="alert_button_request_support">Request support</string>
<string name="alert_button_try_again">Try again</string>
<string name="alert_demo_feature_disabled">This feature is disabled in Demo mode</string>
<string name="alert_failed_to_send_email_title">Failed to send the email</string>
<string name="alert_failed_to_send_transaction_message">Reason: %s</string>
<string name="alert_failed_to_send_transaction_title">Can\'t send a transaction</string>
<string name="alert_manage_tokens_unsupported_blockchain_by_card_message">The selected does not support the %1$s network</string>
<string name="alert_manage_tokens_unsupported_curve_message">To activate the %1$s blockchain\'s cryptographic encryption, you\'ll need to reset the wallet to factory settings. Please withdraw your funds before doing so to ensure that you don\'t lose them, and then complete the reset process. Access to the current wallet will not be possible after the reset.</string>
<string name="alert_manage_tokens_unsupported_message">Tokens in %1$s network are not supported by this card due to firmware limitation.</string>
<string name="alert_negative_app_rate_sent_message">Thank you for your feedback. We will respond as soon as possible</string>
<string name="alert_negative_app_rate_sent_title">Your suggestions were sent</string>
<string name="alert_troubleshooting_scan_card_message">Please try to tap the card exactly as shown in the animation or read our simple guide, or request support. If the problem persists, please request support.</string>
<string name="alert_troubleshooting_scan_card_title">Are you having difficulty scanning your card?</string>
<string name="alert_unsupported_card">This card is not designed to work with this app</string>
<string name="app_settings_default_fee">Default Fee</string>
@ -34,12 +28,8 @@
<string name="app_settings_theme_mode_dark">Dark</string>
<string name="app_settings_theme_mode_light">Light</string>
<string name="app_settings_theme_mode_system">System default</string>
<string name="app_settings_theme_selection_footer">If system is selected, the app will auto-adjust based on your device\'s system settings</string>
<string name="app_settings_theme_selection_system_short">System</string>
<string name="app_settings_theme_selector_title">Theme</string>
<string name="app_settings_title">App settings</string>
<string name="app_settings_warning_subtitle">Go to settings to enable biometric authentication in the Tangem app</string>
<string name="app_settings_warning_title">Enable biometric authentication</string>
<string name="balance_hidden_description">To hide or show your balances, simply flip your device screen down, or switch it off in Settings</string>
<string name="balance_hidden_do_not_show_button">Don\'t show again</string>
<string name="balance_hidden_got_it_button">Got it</string>
@ -48,7 +38,6 @@
<string name="biometric_lockout_warning_description">Please try again in 30 seconds or scan the card</string>
<string name="biometric_lockout_warning_title">Too many attempts</string>
<string name="biometric_unavailable_warning">You have disabled biometric authentication on your phone and will not be able to save wallets in the app. To save wallets, please enable the biometric authentication function in your phone settings.</string>
<string name="biometry_touch_id_reason">Touch ID is used to save your cards in the app</string>
<string name="button_start_backup_process">Start backup process</string>
<string name="buy_token_description">With your bank card or bank account</string>
<plurals name="card_label_card_count">
@ -57,7 +46,6 @@
</plurals>
<string name="card_settings_access_code_recovery_disabled_description">Disable this option if you don\'t want this card to be used to reset access codes on other cards in this wallet. Please note that this will also prevent you from resetting the access code on this card.</string>
<string name="card_settings_access_code_recovery_enabled_description">Allows you to use this card to reset access code on other cards in this wallet</string>
<string name="card_settings_access_code_recovery_footer">Disable the ability to reset the access code on this card or other cards in this wallet</string>
<string name="card_settings_access_code_recovery_title">Access code recovery</string>
<string name="card_settings_action_sheet_reset">Reset</string>
<string name="card_settings_action_sheet_title">Are you sure you want to do this?</string>
@ -86,16 +74,13 @@
<string name="common_approval">Approval</string>
<string name="common_approve">Approve</string>
<string name="common_attention">Attention</string>
<string name="common_back">Back</string>
<string name="common_balance">Balance: %s</string>
<string name="common_balance_title">Balance</string>
<string name="common_biometric_authentication">biometric authentication</string>
<string name="common_biometrics">biometrics</string>
<string name="common_buy">Buy</string>
<string name="common_buy_currency">Go to %1$s</string>
<string name="common_camera_alert_button_settings">Settings</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_camera_denied_alert_title">Camera access denied</string>
<string name="common_cancel">Cancel</string>
<string name="common_claim_rewards">Claim rewards</string>
<string name="common_close">Close</string>
@ -110,7 +95,6 @@
</plurals>
<string name="common_delete">Delete</string>
<string name="common_disabled">Disabled</string>
<string name="common_disconnect">Disconnect</string>
<string name="common_done">Done</string>
<string name="common_enable">Enable</string>
<string name="common_enabled">Enabled</string>
@ -129,7 +113,6 @@
<string name="common_go_to_token">Go to token</string>
<string name="common_import">Import</string>
<string name="common_later">Later</string>
<string name="common_learn_and_earn">Learn &amp; Earn</string>
<string name="common_locked">Locked</string>
<string name="common_main_network">Main network</string>
<string name="common_network_fee_title">Network fee</string>
@ -142,14 +125,12 @@
<string name="common_origin_card">Primary Card</string>
<string name="common_passphrase">Passphrase</string>
<string name="common_paste">Paste</string>
<string name="common_push">Push</string>
<string name="common_range">%1$s-%2$s</string>
<string name="common_read_more">Read more</string>
<string name="common_receive">Receive</string>
<string name="common_reject">Reject</string>
<string name="common_reload">Reload</string>
<string name="common_rename">Rename</string>
<string name="common_retry">Retry</string>
<string name="common_save">Save</string>
<string name="common_save_changes">Save changes</string>
<string name="common_search">Search</string>
@ -178,15 +159,12 @@
<string name="common_unknown_error">There was an error. Please try again.</string>
<string name="common_unreachable">Unreachable</string>
<string name="common_unstake">Unstake</string>
<string name="common_warning">Warning</string>
<string name="common_yes">Yes</string>
<string name="contract_address_copied_message">Contract address copied!</string>
<string name="currency_subtitle_expanded">Available networks</string>
<string name="custom_token_add_token">Add token</string>
<string name="custom_token_contract_address_input_title">Contract address</string>
<string name="custom_token_creation_error_empty_fields">Please fill in all the fields</string>
<string name="custom_token_creation_error_invalid_contract_address">Contract address is invalid</string>
<string name="custom_token_creation_error_invalid_derivation_path">Derivation path 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_custom_derivation">Custom derivation</string>
@ -216,7 +194,6 @@
<string name="details_manage_security_access_code_description">You will have to submit the correct access code before scanning the card</string>
<string name="details_manage_security_long_tap">Long Tap</string>
<string name="details_manage_security_long_tap_description">This mechanism protects against proximity attacks on a card. It will enforce a delay between reception and execution of a command. </string>
<string name="details_manage_security_long_tap_shorter">Long Tap</string>
<string name="details_manage_security_passcode">Passcode</string>
<string name="details_manage_security_passcode_description">Before executing any command entailing a change of the card state, you will have to enter the passcode.</string>
<string name="details_referral_title">Referral program</string>
@ -225,12 +202,10 @@
<string name="details_row_title_cid">Card ID</string>
<string name="details_row_title_contact_to_support">Contact support</string>
<string name="details_row_title_create_backup">Link More Cards</string>
<string name="details_row_title_create_backup_footer">You can synchronize up to three cards into one wallet. It can only be done once.</string>
<string name="details_row_title_currency">App Currency</string>
<string name="details_row_title_flip_to_hide">Flip-to-Hide Balances</string>
<string name="details_row_title_issuer">Issuer</string>
<string name="details_row_title_signed_hashes">Signed</string>
<string name="details_security_management_warning">If you forget the code you will lose access to your funds. Code recovery is not possible.</string>
<string name="details_send_feedback">Send feedback</string>
<string name="details_title">Details</string>
<string name="disclaimer_error_loading">Check your internet connection or switch to a different network</string>
@ -240,7 +215,6 @@
<string name="exchange_tokens_empty_tokens">You haven\'t added any tokens yet. Add tokens via Market to swap</string>
<string name="exchange_tokens_unavailable_tokens_header">Cannot be swapped for %s</string>
<string name="express_by_provider">Provided by</string>
<string name="express_by_provider_placeholder">Provided by %s</string>
<string name="express_cex_status_button_title">Status</string>
<string name="express_choose_providers_subtitle">Tangem offers token swaps via 3rd-party providers according to each provider\'s terms</string>
<string name="express_choose_providers_title">Choose provider</string>
@ -304,7 +278,6 @@
<string name="feedback_subject_support">Feedback</string>
<string name="feedback_subject_support_tangem">Tangem feedback</string>
<string name="feedback_subject_tx_failed">Can\'t send a transaction</string>
<string name="feedback_subject_tx_push_failed">Can\'t push a transaction</string>
<string name="give_permission_current_transaction">Current transaction</string>
<string name="give_permission_fee_footer">The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap.</string>
<string name="give_permission_policy_type_footer">Specify the approve limit for the selected token</string>
@ -319,10 +292,8 @@
<string name="initial_message_change_access_code_body">To change the access code tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_change_passcode_body">To change the passcode tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_create_wallet_body">To create the wallet tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_purge_wallet_body">To reset to factory settings tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_reset_backup_card_header">Tap the card #%s of the wallet</string>
<string name="initial_message_scan_header">Tap to scan</string>
<string name="initial_message_sign_body">To sign tap the card as shown above and do not remove until the end of the operation</string>
<string name="initial_message_sign_header">Tap to sign</string>
<string name="initial_message_tap_header">Tap the card</string>
<string name="key_invalidated_warning_description">You have updated biometrics, scan your card to enter</string>
@ -334,8 +305,6 @@
<string name="koinos_mana_exceeds_koin_balance_title">Mana limit</string>
<string name="koinos_mana_level_description">The Koinos network requires Mana for network fees. Your have %1$s/%2$s Mana</string>
<string name="koinos_mana_level_title">Mana level</string>
<string name="mail_error_no_accounts_body">Please, set up an account to send email</string>
<string name="mail_error_no_accounts_title">No Mail accounts</string>
<string name="main_empty_tokens_list_message">To begin tracking your crypto assets and transactions, add tokens</string>
<string name="main_manage_tokens">Manage tokens</string>
<string name="main_scan_card_warning_view_subtitle">To access all the networks you need to scan the card</string>
@ -358,11 +327,6 @@
<item quantity="one">%1$d of %2$d wallet</item>
<item quantity="other">%1$d of %2$d wallets</item>
</plurals>
<string name="manage_tokens_number_of_wallets_ios">%d of %#@total_wallets@</string>
<plurals name="manage_tokens_number_of_wallets_iostotal_wallets">
<item quantity="one">%d wallet</item>
<item quantity="other">%d wallets</item>
</plurals>
<string name="manage_tokens_remove">Remove</string>
<string name="manage_tokens_search_placeholder">e.g. BTC I trust, hodl I must</string>
<string name="manage_tokens_toast_portfolio_updated">Your portfolio has been updated</string>
@ -454,7 +418,6 @@
<string name="onboarding_activation_error_title">Activation error</string>
<string name="onboarding_add_tokens">Add tokens</string>
<string name="onboarding_alert_message_not_max_backup_cards_added">You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process?</string>
<string name="onboarding_alert_message_old_device">iPhone 7/7+ is not able to create a backup for Tangem Wallet due to some system limitations. Please use another phone to perform this operation. All other functions work stably.</string>
<string name="onboarding_backup_exit_warning">The backup process is partly complete. You can\'t exit it now.</string>
<string name="onboarding_bottom_sheet_passphrase_description">The passphrase is an advanced security feature that crypto wallets use. It adds an extra word or phrase of your own choosing to your already existing recovery phrase to unlock a brand-new set of addresses.</string>
<string name="onboarding_button_add_backup_card">Add a backup card</string>
@ -479,7 +442,6 @@
<string name="onboarding_exit_alert_title">Do you want to exit the activation process?</string>
<string name="onboarding_getting_started">Getting started</string>
<string name="onboarding_linking_error_card_with_wallets">Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup.</string>
<string name="onboarding_navbar_save_wallet">Save your wallet</string>
<string name="onboarding_navbar_title_creating_backup">Creating a backup</string>
<string name="onboarding_seed_button_read_more">Read more about seed phrase</string>
<plurals name="onboarding_seed_generate_message_words_count">
@ -499,7 +461,6 @@
<string name="onboarding_seed_mnemonic_invalid_checksum">Invalid seed phrase. Please check the word order.</string>
<string name="onboarding_seed_mnemonic_wrong_words">Invalid seed phrase. Please check your spelling.</string>
<string name="onboarding_seed_phrase_intro_legacy">Legacy</string>
<string name="onboarding_seed_screenshot_alert">We do not recommend storing the seed phrase as a screenshot due to the high risk of loss or hacking</string>
<string name="onboarding_seed_user_validation_message">To check whether youve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words</string>
<string name="onboarding_seed_user_validation_title">So, lets check</string>
<string name="onboarding_subtitle_no_backup_cards">To start the backup process add up to two backup cards.</string>
@ -535,9 +496,6 @@
<string name="organize_tokens_sort_by_balance">By balance</string>
<string name="organize_tokens_title">Organize tokens</string>
<string name="organize_tokens_ungroup">Ungroup</string>
<string name="push_additional_fee">Additional fee</string>
<string name="push_previous_fee">Previous fee</string>
<string name="push_tx_address_hint">Previous transaction total including fee</string>
<string name="qr_scanner_camera_denied_gallery_button">Select from the gallery</string>
<string name="qr_scanner_camera_denied_settings_button">Settings</string>
<string name="qr_scanner_camera_denied_text">You have not given access to your camera</string>
@ -548,7 +506,6 @@
<string name="referral_button_participate">Participate</string>
<string name="referral_error_failed_to_load_info">Failed to load the information about the referral program. Please try again later.</string>
<string name="referral_error_failed_to_load_info_with_reason">Failed to load the information about the referral program. Error code: %s. Please try again later.</string>
<string name="referral_error_failed_to_participate">Your participation request could not be processed. Error code: %s. Please try again later. If the problem persists — feel free to contact our support.</string>
<string name="referral_expected_awards">Upcoming payments</string>
<string name="referral_friends_bought_title">Your friends bought</string>
<string name="referral_less">Less</string>
@ -584,15 +541,11 @@
<string name="russian_bank_card_warning_title">Russian bank cards are not currently accepted</string>
<string name="save_user_wallet_agreement_access_description">Log into the app and check your balance without scanning the card</string>
<string name="save_user_wallet_agreement_access_title">Access the app</string>
<string name="save_user_wallet_agreement_allow">Allow to use %s</string>
<string name="save_user_wallet_agreement_allow_biometrics">Allow to use biometrics</string>
<string name="save_user_wallet_agreement_code_description">%s will be requested instead of the access code for interactions with your wallet</string>
<string name="save_user_wallet_agreement_code_description_biometrics">Biometrics will be requested instead of the access code for interactions with your wallet</string>
<string name="save_user_wallet_agreement_code_title">Access code</string>
<string name="save_user_wallet_agreement_dont_allow">Don\'t allow</string>
<string name="save_user_wallet_agreement_enroll_biometrics_description">It looks like you have biometric authentication disabled, it is necessary to save wallets</string>
<string name="save_user_wallet_agreement_enroll_biometrics_title">Enable biometric authorization</string>
<string name="save_user_wallet_agreement_header">Would you like to use %s?</string>
<string name="save_user_wallet_agreement_header_biometrics">Would you like to use biometrics?</string>
<string name="save_user_wallet_agreement_notice">Note that making a transaction with your funds will still require your card</string>
<string name="scan_card_settings_button">Scan Card</string>
@ -615,7 +568,6 @@
<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_dismiss_message">Are you sure you want to close the send screen?</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>
@ -628,7 +580,6 @@
<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">From **%s**</string>
<string name="send_from_wallet_android">From</string>
<string name="send_gas_limit">Gas limit</string>
<string name="send_gas_limit_footer">This is the maximum amount of gas that will be spent to complete a transaction or contract. A gas limit prevents unexpected or unlimited charges when executing a transaction.</string>
@ -693,17 +644,17 @@
<string name="staking_active_footer">To unstake your assets, click here.</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>
<string name="staking_details_annual_percentage_rate_info">The annual percentage return you can earn from participating in staking.</string>
<string name="staking_details_apr">APR</string>
<string name="staking_details_apy">APY</string>
<string name="staking_details_apy_info">The annual percentage return you can earn from participating in staking.</string>
<string name="staking_details_available">Available</string>
<string name="staking_details_average_reward_rate">Average Reward Rate</string>
<string name="staking_details_banner_text">What is Staking?</string>
<string name="staking_details_estimated_profit">%s est. profit</string>
<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_on_stake">On stake</string>
<string name="staking_details_reward_claiming">Reward claiming</string>
<string name="staking_details_reward_claiming_info">A way to receive staking rewards. It can be claimed automatically or manually.</string>
<string name="staking_details_reward_schedule">Reward schedule</string>
@ -719,6 +670,7 @@
<string name="staking_native">Native staking</string>
<string name="staking_notification_earn_rewards_text" formatted="false">Staking allow you to earn %1s. Your staking rewards arrive every ~%2s days.</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_rebond">Rebond</string>
<string name="staking_restake">Restake</string>
<string name="staking_restake_rewards">Restake rewards</string>
@ -743,8 +695,6 @@
<string name="story_currencies_title">Thousands of Currencies</string>
<string name="story_finish_description">Use it on the go, anywhere, anytime. No wires or batteries. Just tap the card to your phone when you need your crypto.</string>
<string name="story_finish_title">The Wallet for Everyone</string>
<string name="story_learn_description">Take three lessons, get a discount on your Tangem Wallet, and receive 1INCH tokens to your wallet</string>
<string name="story_learn_learn">Learn</string>
<string name="story_meet_title">Meet Tangem</string>
<string name="story_web3_description">Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services</string>
<string name="story_web3_title">Web 3.0 Compatible</string>
@ -760,7 +710,6 @@
<string name="swapping_high_price_impact_description">Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome.</string>
<string name="swapping_insufficient_funds">Insufficient funds</string>
<string name="swapping_permission_header">Give Permission</string>
<string name="swapping_permit_and_swap">Permit and Swap</string>
<string name="swapping_success_view_title">In progress</string>
<string name="swapping_swap_action">Swap</string>
<string name="swapping_to_title">You receive</string>
@ -778,7 +727,6 @@
<string name="token_button_unavailability_reason_pending_transaction_send">Sending funds will be available once the pending transaction(s) in network %s is complete</string>
<string name="token_button_unavailability_reason_sell_unavailable">Selling %s is not available at the moment. Please check our updates.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Staking %s is not available at the moment. Please check our updates.</string>
<string name="token_details_choose_address">Choose address</string>
<string name="token_details_generate_xpub">Generate XPUB</string>
<string name="token_details_hide_alert_hide">Hide</string>
<string name="token_details_hide_alert_message">You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page.</string>
@ -826,13 +774,6 @@
<string name="user_wallet_list_rename_popup_title">Rename Wallet</string>
<string name="user_wallet_list_unlock_all">Unlock all</string>
<string name="user_wallet_list_unlock_all_with">Unlock all with %s</string>
<string name="voice_over_close_network_fee_settings">Close network fee settings</string>
<string name="voice_over_nothing_to_paste">Nothing to paste from clipboard</string>
<string name="voice_over_open_card_details">Open card details</string>
<string name="voice_over_open_network_fee_settings">Open network fee settings</string>
<string name="voice_over_open_new_wallet_connect_session">Scan QR code to open new WalletConnect session</string>
<string name="voice_over_paste_from_clipboard">Paste address from clipboard</string>
<string name="voice_over_scan_qr_with_address">Scan, QR, code with address</string>
<string name="wallet_balance_blockchain_unreachable_try_later">Blockchain is unreachable. Try later</string>
<string name="wallet_balance_missing_derivation">Scan the card</string>
<string name="wallet_connect_alert_sign_message">Requesting to sign a message.\n\n%s</string>
@ -911,8 +852,6 @@
<string name="warning_hedera_missing_token_association_message_brief">This token must be associated with your Hedera account before you can receive it</string>
<string name="warning_hedera_missing_token_association_title">Associate your token</string>
<string name="warning_hedera_token_association_not_enough_hbar_message">Not enough %s. Top up your Hedera account to associate this token</string>
<string name="warning_long_transaction_message">iPhone 7/7+ cannot sign transactions on this network. To complete this operation, please use a different phone.</string>
<string name="warning_long_transaction_title">Transaction signing unavailable</string>
<string name="warning_low_signatures_message">Only %s signatures are left on this card. You must withdraw all of your funds.</string>
<string name="warning_low_signatures_title">Low signature count</string>
<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>
@ -928,10 +867,6 @@
<string name="warning_no_backup_title">Missing backup</string>
<string name="warning_number_of_signed_hashes_incorrect_message">This card has been previously used for transactions. If received from an untrusted source, consider withdrawing all funds. If it\'s your card, no action is required.</string>
<string name="warning_number_of_signed_hashes_incorrect_title">Card has already signed transactions</string>
<string name="warning_old_card_message">Funds in Tangem cards issued before September 2019 cannot be retrieved on iPhones due to iOS restrictions. Please use an Android phone for retrieval. Cards issued after September 2019 work correctly on both OS.</string>
<string name="warning_old_card_title">iOS restriction for older cards</string>
<string name="warning_old_device_old_card_message">Some iPhone 7/7+ models may have NFC issues during certain operations.</string>
<string name="warning_old_device_old_card_title">Device incompatibility detected</string>
<string name="warning_rate_app_message">Your review keeps us motivated to make Tangem Wallet even better</string>
<string name="warning_rate_app_title">Enjoying Tangem?</string>
<string name="warning_receive_blocked_hedera_token_association_required_message">You must associate your token before receiving tokens</string>
@ -943,10 +878,6 @@
<string name="warning_solana_rent_fee_message">Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free.</string>
<string name="warning_some_networks_unreachable_message">Some networks currently are unreachable. Please try again later.</string>
<string name="warning_some_networks_unreachable_title">Some networks are unreachable</string>
<string name="warning_system_deprecation_title">System update required</string>
<string name="warning_system_deprecation_with_date_message">Support for your version of the operating system will end on %s. To receive future app updates you must update it to the latest version.</string>
<string name="warning_system_update_message">Tangem recommends installing the latest iOS update for stable and safe operation</string>
<string name="warning_system_update_title">System update available</string>
<string name="warning_testnet_card_message">This is a Testnet card. It cannot process transactions and should only be used for testing and development purposes.</string>
<string name="warning_testnet_card_title">For testing purposes only</string>
<string name="welcome_interrupted_backup_alert_discard">Discard</string>

View file

@ -0,0 +1,125 @@
package com.tangem.core.ui.components.notifications
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.colorspace.ColorSpaces
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
/**
* Currency notification component from Design system.
*
* @param config component config
* @param modifier modifier
* @param containerColor container color
*
* @see <a href = "https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=3251-3466&t=eZb7ZPoNE8pQw45Y-4"
* >Figma component</a>
*/
@Composable
fun CurrencyNotification(
config: CurrencyNotificationConfig,
modifier: Modifier = Modifier,
containerColor: Color? = null,
) {
NotificationBaseContainer(
buttonsState = config.buttonsState,
onClick = null,
onCloseClick = null,
modifier = modifier,
containerColor = containerColor,
) {
MainContent(
tokenIconState = config.tokenIconState,
title = config.title,
subtitle = config.subtitle,
)
}
}
@Composable
private fun MainContent(
tokenIconState: CurrencyIconState,
title: TextReference,
subtitle: CurrencyNotificationConfig.AnnotatedSubtitle,
) {
Row {
CurrencyIcon(
state = tokenIconState,
modifier = Modifier.align(alignment = Alignment.CenterVertically),
)
SpacerW(width = TangemTheme.dimens.spacing6)
TextsBlock(title = title, subtitle = subtitle)
}
}
@Composable
private fun TextsBlock(title: TextReference, subtitle: CurrencyNotificationConfig.AnnotatedSubtitle) {
Column(verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing2)) {
Text(
text = title.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.button,
)
val subtitleValue = subtitle.valueProvider()
ClickableText(
text = subtitleValue,
onClick = { subtitle.onClick(subtitleValue, it) },
style = TangemTheme.typography.caption2,
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_Notification() {
TangemThemePreview {
CurrencyNotification(
config = CurrencyNotificationConfig(
title = resourceReference(
R.string.express_exchange_notification_refund_title,
wrappedList("USDT", "Polygon"),
),
subtitle = CurrencyNotificationConfig.AnnotatedSubtitle(
valueProvider = {
buildAnnotatedString {
append("Your transaction amount was refunded in USDT to your wallet due to OKX")
}
},
onClick = { _, _ -> },
),
tokenIconState = CurrencyIconState.TokenIcon(
url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/usd-coin.png",
topBadgeIconResId = R.drawable.ic_polygon_22,
isGrayscale = false,
showCustomBadge = false,
fallbackTint = Color(1.0f, 1.0f, 1.0f, 1.0f, ColorSpaces.Srgb),
fallbackBackground = Color(0.23529412f, 0.28627452f, 0.6117647f, 1.0f, ColorSpaces.Srgb),
),
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = stringReference("Go to token"),
onClick = {},
),
),
)
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.core.ui.components.notifications
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.AnnotatedString
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.extensions.TextReference
/**
* Currency notification component state
*
* @property title title
* @property subtitle subtitle
* @property buttonsState buttons state
* @property tokenIconState token icon state
*
[REDACTED_AUTHOR]
*/
data class CurrencyNotificationConfig(
val title: TextReference,
val subtitle: AnnotatedSubtitle,
val tokenIconState: CurrencyIconState,
val buttonsState: NotificationConfig.ButtonsState,
) {
/**
* Subtitle as [AnnotatedString]
*
* @property valueProvider composable function that provides [AnnotatedString]
* @property onClick lambda be invoked when text in specified position is clicked
*/
data class AnnotatedSubtitle(
val valueProvider: @Composable () -> AnnotatedString,
val onClick: (value: AnnotatedString, position: Int) -> Unit,
)
}

View file

@ -30,17 +30,19 @@ import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState as NotificationButtonsState
/**
* Notification component from Design system.
* Use this for Notification with title, subtitle, clickable or not.
*
* @param config component config
* @param modifier modifier
* @param iconTint icon tint
* @param config component config
* @param modifier modifier
* @param containerColor container color
* @param iconTint icon tint
* @param isEnabled flag that defines if component is clickable
*
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1045-807&t=6CVvYDJe0sB7wBKE-0"
* >Figma component</a>
@ -53,44 +55,33 @@ fun Notification(
iconTint: Color? = null,
isEnabled: Boolean = true,
) {
BaseContainer(
NotificationBaseContainer(
buttonsState = config.buttonsState,
onClick = config.onClick,
onCloseClick = config.onCloseClick,
modifier = modifier,
containerColor = containerColor,
isEnabled = isEnabled,
) {
Column(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
) {
MainContent(
iconResId = config.iconResId,
iconTint = iconTint,
title = config.title,
subtitle = config.subtitle,
isClickableComponent = isEnabled && config.onClick != null,
)
Buttons(state = config.buttonsState, isEnabled = isEnabled)
}
CloseableIconButton(
onClick = config.onCloseClick,
modifier = Modifier.align(alignment = Alignment.TopEnd),
isEnabled = isEnabled,
MainContent(
iconResId = config.iconResId,
iconTint = iconTint,
title = config.title,
subtitle = config.subtitle,
isClickableComponent = isEnabled && config.onClick != null,
)
}
}
@Composable
private fun BaseContainer(
internal fun NotificationBaseContainer(
buttonsState: NotificationConfig.ButtonsState?,
onClick: (() -> Unit)?,
onCloseClick: (() -> Unit)?,
modifier: Modifier = Modifier,
isEnabled: Boolean = true,
containerColor: Color? = null,
content: @Composable BoxScope.() -> Unit,
content: @Composable ColumnScope.() -> Unit,
) {
val tempContainerColor by rememberUpdatedState(
newValue = if (buttonsState != null || onClick != null) {
@ -109,7 +100,22 @@ private fun BaseContainer(
shape = TangemTheme.shapes.roundedCornersXMedium,
color = containerColor ?: tempContainerColor,
) {
Box(content = content)
Box {
Column(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
) {
content()
Buttons(state = buttonsState, isEnabled = isEnabled)
}
CloseableIconButton(
onClick = onCloseClick,
modifier = Modifier.align(alignment = Alignment.TopEnd),
isEnabled = isEnabled,
)
}
}
}

View file

@ -82,7 +82,9 @@ private fun LazyListScope.contentItems(
when (item) {
is TxHistoryState.TxHistoryItemState.GroupTitle -> item.itemKey
is TxHistoryState.TxHistoryItemState.Title -> item.onExploreClick.hashCode()
is TxHistoryState.TxHistoryItemState.Transaction -> item.state.txHash
is TxHistoryState.TxHistoryItemState.Transaction ->
item.state.txHash +
((item.state as? TransactionState.Content)?.hashCode() ?: "")
}
},
contentType = txHistoryItems.itemContentType { it::class.java },

View file

@ -37,7 +37,7 @@ object BigDecimalFormatter {
maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
minimumFractionDigits = 2
isGroupingUsed = true
roundingMode = RoundingMode.DOWN
roundingMode = RoundingMode.HALF_UP
}
return formatter.format(cryptoAmount).let {

View file

@ -6,7 +6,6 @@ import androidx.compose.runtime.MutableState
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale
/**
* Returns push permission requester.
@ -16,22 +15,13 @@ import com.google.accompanist.permissions.shouldShowRationale
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun requestPushPermission(
isFirstTimeAsking: Boolean,
pushPermission: String?,
isClicked: MutableState<Boolean>,
onAllow: () -> Unit,
onDeny: () -> Unit,
onOpenSettings: () -> Unit,
): () -> Unit {
val permissionState = pushPermission?.let { permission ->
val tempPermissionState = rememberPermissionState(permission = permission)
rememberPermissionState(permission = permission) {
when {
it -> onAllow()
!tempPermissionState.status.shouldShowRationale && !isFirstTimeAsking -> onOpenSettings()
else -> onDeny()
}
}
rememberPermissionState(permission = permission)
}
// Check if user granted permission and close bottom sheet
@ -45,7 +35,7 @@ fun requestPushPermission(
}
return if (permissionState == null) {
onOpenSettings
{}
} else {
permissionState::launchPermissionRequest
}

View file

@ -0,0 +1,11 @@
<vector android:autoMirrored="true"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="M21,11V16H7V11H21ZM21,8V6H7V8H21ZM22.41,4.59C22.04,4.21 21.53,4 21,4H7C5.89,4 5,4.89 5,6V16C5,17.1 5.89,18 7,18H21C21.53,18 22.04,17.79 22.41,17.41C22.79,17.04 23,16.53 23,16V6C23,5.47 22.79,4.96 22.41,4.59ZM18,20H3V9H1V20C1,20.53 1.21,21.04 1.59,21.41C1.96,21.79 2.47,22 3,22H18V20Z" />
</vector>

View file

@ -80,24 +80,10 @@ internal class DefaultCardRepository(
return appPreferencesStore.getSyncOrDefault(key = PreferencesKeys.IS_TANGEM_TOS_ACCEPTED_KEY, default = false)
}
override suspend fun isStart2CoinTOSAccepted(cardId: String): Boolean {
return appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.getStart2CoinTOSAcceptedKey(region = getRegion(cardId)),
default = false,
)
}
override suspend fun acceptTangemTOS() {
return appPreferencesStore.store(key = PreferencesKeys.IS_TANGEM_TOS_ACCEPTED_KEY, true)
}
override suspend fun acceptStart2CoinTOS(cardId: String) {
appPreferencesStore.store(
key = PreferencesKeys.getStart2CoinTOSAcceptedKey(region = getRegion(cardId)),
value = true,
)
}
private suspend fun AppPreferencesStore.editUsedCards(cardId: String, update: (UsedCardInfo) -> UsedCardInfo) {
editData { mutablePreferences ->
val usedCards = mutablePreferences.getUsedCards()
@ -127,16 +113,5 @@ internal class DefaultCardRepository(
.firstOrNull { it.cardId == cardId }
}
private fun getRegion(cardId: String): String? {
if (cardId.isEmpty()) return null
return when (cardId[1]) {
'0' -> "fr"
'1' -> "ch"
'2' -> "at"
else -> null
}
}
private fun createDefaultUsedCardInfo(cardId: String) = UsedCardInfo(cardId = cardId)
}

View file

@ -33,19 +33,17 @@ internal class DefaultPromoRepository(
}
override suspend fun getOkxPromoBanner(): PromoBanner? {
// TODO disabled for 5.12, enable for 5.12.1
return null
// return runCatching(dispatchers.io) {
// promoResponseConverter.convert(
// tangemApi.getPromotionInfo(OKX)
// .getOrThrow(),
// )
// }.getOrNull()
return runCatching(dispatchers.io) {
promoResponseConverter.convert(
tangemApi.getPromotionInfo(OKX)
.getOrThrow(),
)
}.getOrNull()
}
private companion object {
private const val CHANGELLY_NAME = "changelly"
private const val TRAVALA = "travala"
// private const val OKX = "okx"
private const val OKX = "okx"
}
}

View file

@ -1,11 +1,6 @@
package com.tangem.data.settings
import android.os.SystemClock
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.PreferencesKeys.getIsFirstTimeAskingPermission
import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionDaysCount
import com.tangem.datasource.local.preferences.PreferencesKeys.getPermissionLaunchCount
import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowInitialPermissionScreen
import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowPermission
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
@ -30,47 +25,11 @@ internal class DefaultPermissionRepository(
)
}
override suspend fun isFirstTimeAskingPermission(permission: String): Boolean =
appPreferencesStore.getSyncOrDefault(
key = getIsFirstTimeAskingPermission(permission),
default = true,
)
override suspend fun setFirstTimeAskingPermission(permission: String, value: Boolean) {
appPreferencesStore.store(
key = getIsFirstTimeAskingPermission(permission),
value = value,
)
}
override suspend fun shouldAskPermission(permission: String): Boolean {
val shouldAskPermission = appPreferencesStore.getSyncOrDefault(getShouldShowPermission(permission), true)
val delayedLaunches = appPreferencesStore.getSyncOrDefault(getPermissionLaunchCount(permission), 0)
val delayedDays = appPreferencesStore.getSyncOrDefault(getPermissionDaysCount(permission), 0)
val currentLaunchCounter = appPreferencesStore.getSyncOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0)
val nowMillis = SystemClock.elapsedRealtime()
val isDaysDelayed = delayedDays < nowMillis
val isLaunchesDelayed = delayedLaunches < currentLaunchCounter
return shouldAskPermission && isDaysDelayed && isLaunchesDelayed
return appPreferencesStore.getSyncOrDefault(getShouldShowPermission(permission), true)
}
override suspend fun neverAskPermission(permission: String) {
appPreferencesStore.store(key = getShouldShowPermission(permission), value = false)
}
override suspend fun delayPermissionAsking(permission: String) {
appPreferencesStore.editData {
val appLaunchCounter = it.getOrDefault(PreferencesKeys.APP_LAUNCH_COUNT_KEY, 0)
val nowMillis = SystemClock.elapsedRealtime()
it[getPermissionLaunchCount(permission)] = appLaunchCounter + DELAY_LAUNCH_COUNT
it[getPermissionDaysCount(permission)] = nowMillis + DELAY_DAYS_COUNT
}
}
private companion object {
const val DELAY_LAUNCH_COUNT = 5
const val DELAY_DAYS_COUNT = 3L * 24 * 3600 * 1000 // 3 days in millis
}
}

View file

@ -39,7 +39,7 @@ import kotlinx.coroutines.withContext
import timber.log.Timber
import com.tangem.blockchain.common.FeePaidCurrency as FeePaidSdkCurrency
@Suppress("LargeClass", "LongParameterList")
@Suppress("LargeClass", "LongParameterList", "TooManyFunctions")
internal class DefaultCurrenciesRepository(
private val tangemTechApi: TangemTechApi,
private val tangemExpressApi: TangemExpressApi,
@ -53,6 +53,7 @@ internal class DefaultCurrenciesRepository(
private val demoConfig = DemoConfig()
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory()
private val cryptoCurrencyFactory = CryptoCurrencyFactory()
private val cardCurrenciesFactory = CardCryptoCurrenciesFactory(demoConfig)
private val userTokensResponseFactory = UserTokensResponseFactory()
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
@ -61,18 +62,6 @@ internal class DefaultCurrenciesRepository(
private val isMultiCurrencyWalletCurrenciesFetching = MutableStateFlow(
value = emptyMap<UserWalletId, Boolean>(),
)
private val parallelTransactionsEnabledBlockchains = setOf(
Blockchain.Ethereum,
Blockchain.EthereumTestnet,
Blockchain.Polygon,
Blockchain.PolygonTestnet,
Blockchain.Arbitrum,
Blockchain.ArbitrumTestnet,
Blockchain.Binance,
Blockchain.BinanceTestnet,
Blockchain.Tron,
Blockchain.TronTestnet,
)
override suspend fun saveTokens(
userWalletId: UserWalletId,
@ -137,7 +126,7 @@ internal class DefaultCurrenciesRepository(
return newTokens
.filterNot { savedCurrencies.hasCoinForToken(it) } // tokens without coins
.mapNotNull {
CryptoCurrencyFactory().createCoin(
cryptoCurrencyFactory.createCoin(
blockchain = getBlockchain(networkId = it.network.id),
extraDerivationPath = it.network.derivationPath.value,
derivationStyleProvider = getUserWallet(userWalletId).scanResponse.derivationStyleProvider,
@ -391,7 +380,8 @@ internal class DefaultCurrenciesRepository(
val outgoingTransactions = cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing }
outgoingTransactions.isNotEmpty()
}
parallelTransactionsEnabledBlockchains.contains(blockchain) -> false
blockchain.isEvm() -> false
blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet -> false
else -> coinStatus?.value?.hasCurrentNetworkTransactions == true
}
}
@ -426,12 +416,44 @@ internal class DefaultCurrenciesRepository(
}
override fun createTokenCurrency(cryptoCurrency: CryptoCurrency.Token, network: Network): CryptoCurrency.Token {
return CryptoCurrencyFactory().createToken(
return cryptoCurrencyFactory.createToken(
cryptoCurrency = cryptoCurrency,
network = network,
)
}
override suspend fun createTokenCurrency(
userWalletId: UserWalletId,
contractAddress: String,
networkId: String,
): CryptoCurrency.Token {
val userWallet = getUserWallet(userWalletId)
val token = withContext(dispatchers.io) {
val foundToken = tangemTechApi.getCoins(
contractAddress = contractAddress,
networkId = networkId,
)
.getOrThrow()
.coins
.firstOrNull()
?: error("Token not found")
val network = foundToken.networks.firstOrNull { it.networkId == networkId } ?: error("Network not found")
CryptoCurrencyFactory.Token(
symbol = foundToken.symbol,
name = foundToken.name,
contractAddress = contractAddress,
decimals = network.decimalCount?.toInt() ?: error("Decimals not found"),
id = foundToken.id,
)
}
return cryptoCurrencyFactory.createToken(
token = token,
networkId = networkId,
extraDerivationPath = null,
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
) ?: error("Unable to create token")
}
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<List<CryptoCurrency>> {
return userTokensStore.get(userWallet.walletId).map { storedTokens ->
responseCurrenciesFactory.createCurrencies(

View file

@ -99,7 +99,7 @@ class CryptoCurrencyFactory {
decimals = cryptoCurrency.decimals,
id = cryptoCurrency.id.rawCurrencyId,
)
val blockchain = Blockchain.fromNetworkId(cryptoCurrency.network.id.value) ?: Blockchain.Unknown
val blockchain = Blockchain.fromNetworkId(cryptoCurrency.network.backendId) ?: Blockchain.Unknown
val id = getTokenId(network, sdkToken)
return CryptoCurrency.Token(
id = id,

View file

@ -24,7 +24,6 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.math.BigDecimal
import java.math.BigInteger
import com.tangem.blockchain.blockchains.tron.TransactionType as SdkTransactionType
@ -41,7 +40,6 @@ internal class DefaultTransactionRepository(
destination: String,
userWalletId: UserWalletId,
network: Network,
isSwap: Boolean,
txExtras: TransactionExtras?,
hash: String?,
): TransactionData.Uncompiled? = withContext(coroutineDispatcherProvider.io) {
@ -58,7 +56,6 @@ internal class DefaultTransactionRepository(
memo = memo,
destination = destination,
network = network,
isSwap = isSwap,
txExtras = txExtras,
hash = hash,
)
@ -91,7 +88,6 @@ internal class DefaultTransactionRepository(
memo = memo,
destination = destination,
network = network,
isSwap = isSwap,
txExtras = txExtras,
hash = hash,
)
@ -158,23 +154,15 @@ internal class DefaultTransactionRepository(
memo: String?,
destination: String,
network: Network,
isSwap: Boolean,
txExtras: TransactionExtras?,
hash: String?,
): TransactionData.Uncompiled {
// TODO: refactor workaround to use general mechanism in bsdk for build tx for DEX
val txAmount = if (isSwap) {
createAmountForSwap(amount)
} else {
amount
}
if (txExtras != null && memo != null) {
// throw error for now to avoid programmers errors when use extras
error("Both txExtras and memo provided, use only one of them")
}
val extras = txExtras ?: getMemoExtras(network.id.value, memo)
return createTransaction(txAmount, fee, destination).copy(
return createTransaction(amount, fee, destination).copy(
hash = hash,
extras = extras,
)
@ -203,19 +191,4 @@ internal class DefaultTransactionRepository(
else -> null
}
}
private fun createAmountForSwap(amount: Amount): Amount {
return when (amount.type) {
is AmountType.Coin -> amount
else -> {
// 1. when creates swap amount for NonNativeToken, amount should be ZERO
// 2. Amount has .Coin type, as workaround to use destinationAddress in bsdk, not contractAddress
Amount(
currencySymbol = amount.currencySymbol,
value = BigDecimal.ZERO,
decimals = amount.decimals,
)
}
}
}
}

View file

@ -85,6 +85,7 @@ internal class TxHistoryPagingSource(
currency = sourceParams.currency,
)
.filterUnconfirmedTransaction()
.sortedByDescending { it.timestampInMillis }
.filterIfTxAlreadyAdded(apiItems = items)
return if (recentItems.isEmpty()) {

View file

@ -26,10 +26,5 @@ interface CardRepository {
@Throws
suspend fun isTangemTOSAccepted(): Boolean
@Throws
suspend fun isStart2CoinTOSAccepted(cardId: String): Boolean
suspend fun acceptTangemTOS()
suspend fun acceptStart2CoinTOS(cardId: String)
}

View file

@ -1,5 +1,6 @@
package com.tangem.domain.exchange
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.tokens.model.CryptoCurrency
/**
@ -7,7 +8,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
*/
interface RampStateManager {
fun availableForBuy(cryptoCurrency: CryptoCurrency): Boolean
fun availableForBuy(scanResponse: ScanResponse, cryptoCurrency: CryptoCurrency): Boolean
fun availableForSell(cryptoCurrency: CryptoCurrency): Boolean
}

View file

@ -1,13 +0,0 @@
package com.tangem.domain.settings
import arrow.core.Either
import com.tangem.domain.settings.repositories.PermissionRepository
class DelayPermissionRequestUseCase(
private val repository: PermissionRepository,
) {
suspend operator fun invoke(permission: String): Either<Throwable, Unit> = Either.catch {
repository.delayPermissionAsking(permission)
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.domain.settings
import arrow.core.Either
import com.tangem.domain.settings.repositories.PermissionRepository
class IsFirstTimeAskingPermissionUseCase(private val repository: PermissionRepository) {
suspend operator fun invoke(permission: String): Either<Throwable, Boolean> = Either.catch {
repository.isFirstTimeAskingPermission(permission)
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.domain.settings
import arrow.core.Either
import com.tangem.domain.settings.repositories.PermissionRepository
class SetFirstTimeAskingPermissionUseCase(private val repository: PermissionRepository) {
suspend operator fun invoke(permission: String): Either<Throwable, Unit> = Either.catch {
repository.setFirstTimeAskingPermission(permission, false)
}
}

View file

@ -13,18 +13,6 @@ interface PermissionRepository {
*/
suspend fun neverInitiallyShowPermissionScreen(permission: String)
/**
* Indicates which time [permission] was asked via platform dialog.
* NOTE: Use this method to indicate either reroute to settings or display platform dialog.
*/
suspend fun isFirstTimeAskingPermission(permission: String): Boolean
/**
* Sets value indicating that [permission] was asked via platform dialog.
* NOTE: Use this method to indicate either reroute to settings or display platform dialog.
*/
suspend fun setFirstTimeAskingPermission(permission: String, value: Boolean)
/**
* Is clear to ask [permission].
* User could already granted or permanently denied permission
@ -36,9 +24,4 @@ interface PermissionRepository {
* Permanently deny [permission] and never request again
*/
suspend fun neverAskPermission(permission: String)
/**
* Delay next [permission] request for some time or active sessions
*/
suspend fun delayPermissionAsking(permission: String)
}

View file

@ -79,13 +79,35 @@ class AddCryptoCurrenciesUseCase(
.toNonEmptyListOrNull()
?: return@either
catch({ currenciesRepository.addCurrencies(userWalletId, currenciesToAdd) }) {
raise(it)
}
addCurrencies(userWalletId, currenciesToAdd)
refreshUpdatedNetworks(userWalletId, currenciesToAdd, existingCurrencies)
}
suspend operator fun invoke(
userWalletId: UserWalletId,
contractAddress: String,
networkId: String,
): Either<Throwable, CryptoCurrency> = either {
val existingCurrencies =
catch({ currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId) }) {
raise(it)
}
val foundToken = existingCurrencies
.filterIsInstance<CryptoCurrency.Token>()
.firstOrNull {
it.network.backendId == networkId &&
!it.isCustom &&
it.contractAddress.equals(contractAddress, true)
}
if (foundToken != null) {
return@either foundToken
}
val tokenToAdd = createTokenCurrency(userWalletId, contractAddress, networkId)
addCurrencies(userWalletId, listOf(tokenToAdd))
refreshUpdatedNetworks(userWalletId, listOf(tokenToAdd), existingCurrencies)
tokenToAdd
}
/**
* Refreshes the network statuses for tokens that have corresponding coins in the
* [existingCurrencies] list.
@ -117,6 +139,33 @@ class AddCryptoCurrenciesUseCase(
}
}
private suspend fun Raise<Throwable>.createTokenCurrency(
userWalletId: UserWalletId,
contractAddress: String,
networkId: String,
): CryptoCurrency.Token {
return catch(
block = {
currenciesRepository.createTokenCurrency(
userWalletId = userWalletId,
contractAddress = contractAddress,
networkId = networkId,
)
},
catch = {
raise(it)
},
)
}
private suspend fun Raise<Throwable>.addCurrencies(userWalletId: UserWalletId, tokens: List<CryptoCurrency>) {
catch(
{ currenciesRepository.addCurrencies(userWalletId, tokens) },
) {
raise(it)
}
}
/**
* Determines if the [existingCurrencies] list contains a coin that corresponds
* to the given [token].

View file

@ -106,7 +106,7 @@ class GetCryptoCurrencyActionsUseCase(
return listOf(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
}
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Unreachable) {
return getActionsForUnreachableCurrency(cryptoCurrencyStatus, needAssociateAsset)
return getActionsForUnreachableCurrency(userWallet, cryptoCurrencyStatus, needAssociateAsset)
}
val activeList = mutableListOf<TokenActionsState.ActionState>()
@ -168,7 +168,7 @@ class GetCryptoCurrencyActionsUseCase(
}
// buy
if (rampManager.availableForBuy(cryptoCurrency)) {
if (rampManager.availableForBuy(userWallet.scanResponse, cryptoCurrency)) {
activeList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None))
} else {
disabledList.add(
@ -222,6 +222,7 @@ class GetCryptoCurrencyActionsUseCase(
}
private fun getActionsForUnreachableCurrency(
userWallet: UserWallet,
cryptoCurrencyStatus: CryptoCurrencyStatus,
needAssociateAsset: Boolean,
): List<TokenActionsState.ActionState> {
@ -230,7 +231,7 @@ class GetCryptoCurrencyActionsUseCase(
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
actionsList.add(TokenActionsState.ActionState.CopyAddress(ScenarioUnavailabilityReason.None))
}
if (rampManager.availableForBuy(cryptoCurrencyStatus.currency)) {
if (rampManager.availableForBuy(userWallet.scanResponse, cryptoCurrencyStatus.currency)) {
actionsList.add(TokenActionsState.ActionState.Buy(ScenarioUnavailabilityReason.None))
} else {
actionsList.add(

View file

@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.Flow
/**
* Repository for everything related to the tokens of user wallet
* */
@Suppress("TooManyFunctions")
interface CurrenciesRepository {
/**
@ -213,4 +214,13 @@ interface CurrenciesRepository {
* Creates token [cryptoCurrency] based on current token and [network] it`s will be added
*/
fun createTokenCurrency(cryptoCurrency: CryptoCurrency.Token, network: Network): CryptoCurrency.Token
/**
* Creates token [cryptoCurrency] based on [contractAddress] and [networkId] it`s will be added
*/
suspend fun createTokenCurrency(
userWalletId: UserWalletId,
contractAddress: String,
networkId: String,
): CryptoCurrency.Token
}

View file

@ -136,4 +136,12 @@ internal class MockCurrenciesRepository(
override fun createTokenCurrency(cryptoCurrency: CryptoCurrency.Token, network: Network): CryptoCurrency.Token {
return cryptoCurrency
}
override suspend fun createTokenCurrency(
userWalletId: UserWalletId,
contractAddress: String,
networkId: String,
): CryptoCurrency.Token {
error("not implemented")
}
}

View file

@ -18,7 +18,6 @@ interface TransactionRepository {
destination: String,
userWalletId: UserWalletId,
network: Network,
isSwap: Boolean,
txExtras: TransactionExtras?,
hash: String?,
): TransactionData.Uncompiled?

View file

@ -24,7 +24,6 @@ class CreateTransactionUseCase(
userWalletId: UserWalletId,
network: Network,
txExtras: TransactionExtras? = null,
isSwap: Boolean = false,
hash: String? = null,
) = Either.catch {
requireNotNull(
@ -35,7 +34,6 @@ class CreateTransactionUseCase(
destination = destination,
userWalletId = userWalletId,
network = network,
isSwap = isSwap,
txExtras = txExtras,
hash = hash,
),

View file

@ -14,6 +14,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.demo.DemoTransactionSender
import com.tangem.domain.tokens.model.Network
@ -37,7 +38,10 @@ class SendTransactionUseCase(
userWallet: UserWallet,
network: Network,
): Either<SendTransactionError?, String> {
val signer = cardSdkConfigRepository.getCommonSigner(cardId = null)
val card = userWallet.scanResponse.card
val isCardNotBackedUp = card.backupStatus?.isActive != true && !card.isTangemTwins
val signer = cardSdkConfigRepository.getCommonSigner(cardId = card.cardId.takeIf { isCardNotBackedUp })
val linkedTerminal = cardSdkConfigRepository.isLinkedTerminal()
if (userWallet.scanResponse.card.isStart2Coin) {
@ -105,22 +109,7 @@ class SendTransactionUseCase(
}
val error = result.error as? BlockchainSdkError ?: return SendTransactionError.UnknownError()
return when (error) {
is BlockchainSdkError.WrappedTangemError -> {
if (error.code == USER_CANCELLED_ERROR_CODE) {
SendTransactionError.UserCancelledError
} else {
val tangemError = error.tangemError
if (tangemError is TangemSdkError) {
val resource = tangemError.localizedDescriptionRes()
val resId = resource.resId ?: R.string.common_unknown_error
val resArgs = resource.args.map { it.value }
val textReference = resourceReference(resId, wrappedList(resArgs))
SendTransactionError.TangemSdkError(tangemError.code, textReference)
} else {
SendTransactionError.BlockchainSdkError(error.code, tangemError.customMessage)
}
}
}
is BlockchainSdkError.WrappedTangemError -> parseWrappedError(error)
is BlockchainSdkError.CreateAccountUnderfunded -> {
val minAmount = error.minReserve
val minValue = minAmount.value?.toFormattedString(minAmount.decimals).orEmpty()
@ -134,4 +123,26 @@ class SendTransactionUseCase(
}
}
}
private fun parseWrappedError(error: BlockchainSdkError.WrappedTangemError): SendTransactionError {
return if (error.code == USER_CANCELLED_ERROR_CODE) {
SendTransactionError.UserCancelledError
} else {
when (val tangemError = error.tangemError) {
is TangemSdkError -> {
val resource = tangemError.localizedDescriptionRes()
val resId = resource.resId ?: R.string.common_unknown_error
val resArgs = resource.args.map { it.value }
val textReference = resourceReference(resId, wrappedList(resArgs))
SendTransactionError.TangemSdkError(tangemError.code, textReference)
}
is BlockchainSdkError.WrappedTangemError -> {
parseWrappedError(tangemError) // todo remove when sdk errors are revised
}
else -> {
SendTransactionError.BlockchainSdkError(error.code, tangemError.customMessage)
}
}
}
}
}

View file

@ -8,25 +8,32 @@ import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.models.UpdateWalletError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
/**
* Use case for rename user wallet
*
* @property userWalletsListManager user wallets list manager
*/
class RenameWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
class RenameWalletUseCase(
private val userWalletsListManager: UserWalletsListManager,
private val dispatchers: CoroutineDispatcherProvider,
) {
suspend operator fun invoke(userWalletId: UserWalletId, name: String): Either<UpdateWalletError, UserWallet> =
either {
val existingNames = userWalletsListManager.userWalletsSync
withContext(dispatchers.io) {
either {
val existingNames = userWalletsListManager.userWalletsSync
ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) {
UpdateWalletError.NameAlreadyExists
}
ensure(existingNames.none { it.name == name && it.walletId != userWalletId }) {
UpdateWalletError.NameAlreadyExists
}
when (val result = userWalletsListManager.update(userWalletId) { it.copy(name = name) }) {
is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error))
is CompletionResult.Success -> result.data
when (val result = userWalletsListManager.update(userWalletId) { it.copy(name = name) }) {
is CompletionResult.Failure -> raise(UpdateWalletError.DataError(result.error))
is CompletionResult.Success -> result.data
}
}
}
}

View file

@ -87,7 +87,7 @@ internal class ItemsBuilder @Inject constructor(
DetailsItemUM.Basic.Item(
id = "send_feedback",
block = BlockUM(
text = resourceReference(R.string.details_send_feedback),
text = resourceReference(R.string.details_row_title_contact_to_support),
iconRes = R.drawable.ic_comment_24,
onClick = onClick,
),

View file

@ -21,7 +21,8 @@ internal fun List<UserWallet>.toUiModels(
isBalancesHidden: Boolean = false,
): ImmutableList<UserWalletUM> = this.map { model ->
val balance = balances[model.walletId]
model.mapToUiModel(
model.toUiModel(
balance = balance,
appCurrency = appCurrency,
isLoading = isLoading,
@ -30,7 +31,7 @@ internal fun List<UserWallet>.toUiModels(
)
}.toImmutableList()
private fun UserWallet.mapToUiModel(
private fun UserWallet.toUiModel(
balance: TotalFiatBalance?,
appCurrency: AppCurrency?,
isLoading: Boolean,
@ -66,9 +67,9 @@ private fun UserWallet.getInfo(
)
return when {
isBalanceHidden -> combinedReference(cardCountRef, dividerRef, stringReference(STARS))
isLocked -> combinedReference(cardCountRef, dividerRef, resourceReference(R.string.common_locked))
isLoading -> cardCountRef
isBalanceHidden -> combinedReference(cardCountRef, dividerRef, stringReference(STARS))
else -> getBalanceInfo(balance, appCurrency, cardCountRef, dividerRef)
}
}
@ -80,7 +81,7 @@ private fun getBalanceInfo(
dividerRef: TextReference,
): TextReference {
val amount = when (balance) {
is TotalFiatBalance.Loaded -> balance.amount.takeIf { balance.isAllAmountsSummarized }
is TotalFiatBalance.Loaded -> balance.amount
is TotalFiatBalance.Failed,
is TotalFiatBalance.Loading,
null,

View file

@ -7,7 +7,6 @@ import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.disclaimer.impl.model.DisclaimerModel
import com.tangem.features.disclaimer.impl.ui.DisclaimerScreen
@ -18,7 +17,6 @@ import dagger.assisted.AssistedInject
internal class DefaultDisclaimerComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
@Assisted private val params: DisclaimerComponent.Params,
private val appFinisher: AppFinisher,
) : DisclaimerComponent, AppComponentContext by context {
private val model: DisclaimerModel = getOrCreateModel(params)
@ -27,13 +25,7 @@ internal class DefaultDisclaimerComponent @AssistedInject constructor(
override fun Content(modifier: Modifier) {
val state by model.state.collectAsStateWithLifecycle()
BackHandler {
if (params.isTosAccepted) {
state.popBack()
} else {
appFinisher.finish()
}
}
BackHandler(onBack = state.popBack)
DisclaimerScreen(state = state)
}

View file

@ -5,6 +5,7 @@ import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.settings.NeverRequestPermissionUseCase
import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase
@ -17,12 +18,14 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@ComponentScoped
@Suppress("LongParameterList")
internal class DisclaimerModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val cardRepository: CardRepository,
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
private val appFinisher: AppFinisher,
paramsContainer: ParamsContainer,
) : Model() {
@ -33,23 +36,34 @@ internal class DisclaimerModel @Inject constructor(
onAccept = ::onAccept,
url = DISCLAIMER_URL,
isTosAccepted = params.isTosAccepted,
popBack = router::pop,
popBack = ::popBack,
),
)
private fun onAccept(shouldAskPushPermission: Boolean) {
modelScope.launch {
private fun onAccept(shouldAskPushPermission: Boolean) = modelScope.launch {
if (params.isTosAccepted) {
router.pop()
} else {
cardRepository.acceptTangemTOS()
if (shouldAskPushPermission) {
router.push(AppRoute.PushNotification)
} else {
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
neverRequestPermissionUseCase(PUSH_PERMISSION)
router.push(AppRoute.Home)
router.replaceAll(AppRoute.Home)
}
}
}
private fun popBack() {
if (params.isTosAccepted) {
router.pop()
} else {
appFinisher.finish()
}
}
private companion object {
const val DISCLAIMER_URL = "https://tangem.com/tangem_tos.html"
}

View file

@ -17,19 +17,15 @@ sealed class PushNotificationAnalyticEvents(
),
)
data class ButtonLater(
data class ButtonCancel(
val source: AnalyticsParam.ScreensSources,
) : PushNotificationAnalyticEvents(
event = "Button - Later",
event = "Button - Cancel",
params = mapOf(
AnalyticsParam.SOURCE to source.value,
),
)
data object ButtonCancel : PushNotificationAnalyticEvents(
event = "Button - Cancel",
)
data class PermissionStatus(
val isAllowed: Boolean,
) : PushNotificationAnalyticEvents(

View file

@ -26,10 +26,9 @@ internal class PushNotificationsFragment : ComposeFragment() {
NavigationBar3ButtonsScrim()
PushNotificationsScreen(
onRequest = viewModel::onRequest,
onRequestLater = viewModel::onRequestLater,
onNeverRequest = viewModel::onNeverRequest,
onAllowPermission = viewModel::onAllowPermission,
onDenyPermission = viewModel::onDenyPermission,
onOpenSettings = viewModel::openSettings,
)
}

View file

@ -17,18 +17,15 @@ import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun PushNotificationsScreen(
onRequest: () -> Unit,
onRequestLater: () -> Unit,
onNeverRequest: () -> Unit,
onAllowPermission: () -> Unit,
onDenyPermission: () -> Unit,
onOpenSettings: () -> Unit,
) {
val isClicked = remember { mutableStateOf(false) }
val requestPushPermission = requestPushPermission(
isFirstTimeAsking = true,
isClicked = isClicked,
onAllow = onAllowPermission,
onDeny = onDenyPermission,
onOpenSettings = onOpenSettings,
pushPermission = getPushPermissionOrNull(),
)
@ -54,8 +51,8 @@ internal fun PushNotificationsScreen(
},
),
secondaryButton = ShowcaseButtonModel(
buttonText = resourceReference(R.string.common_later),
onClick = onRequestLater,
buttonText = resourceReference(R.string.common_cancel),
onClick = onNeverRequest,
),
modifier = Modifier.systemBarsPadding(),
)

View file

@ -4,11 +4,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.navigation.settings.SettingsManager
import com.tangem.domain.settings.DelayPermissionRequestUseCase
import com.tangem.domain.settings.NeverRequestPermissionUseCase
import com.tangem.domain.settings.NeverToInitiallyAskPermissionUseCase
import com.tangem.domain.settings.SetFirstTimeAskingPermissionUseCase
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.pushnotifications.impl.navigation.DefaultPushNotificationsRouter
@ -19,12 +16,9 @@ import javax.inject.Inject
@Suppress("LongParameterList")
@HiltViewModel
internal class PushNotificationViewModel @Inject constructor(
private val setFirstTimeAskingPermissionUseCase: SetFirstTimeAskingPermissionUseCase,
private val delayPermissionRequestUseCase: DelayPermissionRequestUseCase,
private val neverRequestPermissionUseCase: NeverRequestPermissionUseCase,
private val neverToInitiallyAskPermissionUseCase: NeverToInitiallyAskPermissionUseCase,
private val router: DefaultPushNotificationsRouter,
private val settingsManager: SettingsManager,
private val analyticHandler: AnalyticsEventHandler,
) : ViewModel(), PushNotificationsClickIntents {
@ -32,18 +26,14 @@ internal class PushNotificationViewModel @Inject constructor(
analyticHandler.send(
PushNotificationAnalyticEvents.ButtonAllow(AnalyticsParam.ScreensSources.Stories),
)
viewModelScope.launch {
setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION)
}
}
override fun onRequestLater() {
override fun onNeverRequest() {
analyticHandler.send(
PushNotificationAnalyticEvents.ButtonLater(AnalyticsParam.ScreensSources.Stories),
PushNotificationAnalyticEvents.ButtonCancel(AnalyticsParam.ScreensSources.Stories),
)
viewModelScope.launch {
delayPermissionRequestUseCase(PUSH_PERMISSION)
setFirstTimeAskingPermissionUseCase(PUSH_PERMISSION)
neverRequestPermissionUseCase(PUSH_PERMISSION)
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
router.openHome()
}
@ -65,11 +55,9 @@ internal class PushNotificationViewModel @Inject constructor(
PushNotificationAnalyticEvents.PermissionStatus(isAllowed = false),
)
viewModelScope.launch {
delayPermissionRequestUseCase(PUSH_PERMISSION)
neverRequestPermissionUseCase(PUSH_PERMISSION)
neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION)
router.openHome()
}
}
override fun openSettings() = settingsManager.openSettings()
}

View file

@ -3,11 +3,9 @@ package com.tangem.features.pushnotifications.impl.presentation.viewmodel
internal interface PushNotificationsClickIntents {
fun onRequest()
fun onRequestLater()
fun onNeverRequest()
fun onAllowPermission()
fun onDenyPermission()
fun openSettings()
}

View file

@ -32,6 +32,7 @@ dependencies {
implementation(deps.arrow.core)
implementation(deps.jodatime)
implementation(deps.timber)
implementation(deps.tangem.card.core)
/** DI */
implementation(deps.hilt.android)

View file

@ -1,10 +1,12 @@
package com.tangem.feature.referral.domain
import arrow.core.getOrElse
import com.tangem.common.core.TangemSdkError
import com.tangem.domain.card.DerivePublicKeysUseCase
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.referral.domain.errors.ReferralError
import com.tangem.feature.referral.domain.models.ReferralData
import com.tangem.feature.referral.domain.models.TokenData
import com.tangem.lib.crypto.UserWalletManager
@ -42,7 +44,7 @@ internal class ReferralInteractorImpl(
val cryptoCurrency = repository.getCryptoCurrency(userWalletId = userWallet.walletId, tokenData = tokenData)
derivePublicKeysUseCase(userWallet.walletId, listOfNotNull(cryptoCurrency)).getOrElse {
Timber.e("Failed to derive public keys: $it")
throw it
throw it.mapToDomainError()
}
addCryptoCurrenciesUseCase(
@ -67,4 +69,13 @@ internal class ReferralInteractorImpl(
tokensForReferral.clear()
tokensForReferral.addAll(tokens)
}
private fun Throwable.mapToDomainError(): ReferralError {
if (this !is TangemSdkError) return ReferralError.DataError(this)
return if (this is TangemSdkError.UserCancelled) {
ReferralError.UserCancelledException
} else {
ReferralError.SdkError
}
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.feature.referral.domain.errors
sealed class ReferralError : Exception() {
data object UserCancelledException : ReferralError()
data object SdkError : ReferralError()
data class DataError(val throwable: Throwable) : ReferralError()
}

View file

@ -13,6 +13,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.referral.analytics.ReferralEvents
import com.tangem.feature.referral.domain.ReferralInteractor
import com.tangem.feature.referral.domain.errors.ReferralError
import com.tangem.feature.referral.domain.models.DiscountType
import com.tangem.feature.referral.domain.models.ReferralData
import com.tangem.feature.referral.domain.models.ReferralInfo
@ -21,7 +22,6 @@ import com.tangem.feature.referral.models.ReferralStateHolder
import com.tangem.feature.referral.models.ReferralStateHolder.ErrorSnackbar
import com.tangem.feature.referral.models.ReferralStateHolder.ReferralInfoState
import com.tangem.feature.referral.router.ReferralRouter
import com.tangem.lib.crypto.models.errors.UserCancelledException
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import dagger.hilt.android.lifecycle.HiltViewModel
@ -95,7 +95,7 @@ internal class ReferralViewModel @Inject constructor(
runCatching(dispatchers.io) { referralInteractor.startReferral(userWalletId) }
.onSuccess(::showContent)
.onFailure { throwable ->
if (throwable is UserCancelledException) {
if (throwable is ReferralError.UserCancelledException) {
lastReferralData?.let { referralData ->
showContent(referralData)
}

View file

@ -20,15 +20,10 @@ internal object InitialStakingStatePreview {
endText = TextReference.Str("15 SOL"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_apy,
startText = TextReference.Res(R.string.staking_details_apy),
id = R.string.staking_details_annual_percentage_rate,
startText = TextReference.Res(R.string.staking_details_annual_percentage_rate),
endText = TextReference.Str("2.54-5.12%"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_on_stake,
startText = TextReference.Res(R.string.staking_details_on_stake),
endText = TextReference.Str("0 SOL"),
),
RoundedListWithDividersItemData(
id = R.string.staking_details_unbonding_period,
startText = TextReference.Res(R.string.staking_details_unbonding_period),

View file

@ -9,7 +9,6 @@ import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.staking.impl.R
@ -22,7 +21,6 @@ import com.tangem.features.staking.impl.presentation.state.converters.YieldBalan
import com.tangem.features.staking.impl.presentation.state.previewdata.ConfirmationStatePreviewData
import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents
import com.tangem.utils.Provider
import com.tangem.utils.extensions.orZero
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
@ -91,7 +89,6 @@ internal class SetInitialDataStateTransformer(
return listOfNotNull(
createAvailableItem(cryptoCurrencyStatus),
createApyItem(),
createOnStakeItem(cryptoCurrencyStatus, yieldBalance),
createUnbondingPeriodItem(),
createMinimumRequirementItem(cryptoCurrencyStatus),
createRewardClaimingItem(),
@ -116,30 +113,13 @@ internal class SetInitialDataStateTransformer(
private fun createApyItem(): RoundedListWithDividersItemData {
return RoundedListWithDividersItemData(
id = R.string.staking_details_apy,
startText = TextReference.Res(R.string.staking_details_apy),
id = R.string.staking_details_annual_percentage_rate,
startText = TextReference.Res(R.string.staking_details_annual_percentage_rate),
endText = getAprRange(),
iconClick = { clickIntents.onInfoClick(InfoType.APY) },
)
}
private fun createOnStakeItem(
cryptoCurrencyStatus: CryptoCurrencyStatus,
yieldBalance: YieldBalance?,
): RoundedListWithDividersItemData {
return RoundedListWithDividersItemData(
id = R.string.staking_details_on_stake,
startText = TextReference.Res(R.string.staking_details_on_stake),
endText = TextReference.Str(
value = BigDecimalFormatter.formatCryptoAmount(
cryptoAmount = (yieldBalance as? YieldBalance.Data)?.getTotalStakingBalance().orZero(),
cryptoCurrency = cryptoCurrencyStatus.currency.symbol,
decimals = cryptoCurrencyStatus.currency.decimals,
),
),
)
}
private fun createUnbondingPeriodItem(): RoundedListWithDividersItemData {
return RoundedListWithDividersItemData(
id = R.string.staking_details_unbonding_period,

Some files were not shown because too many files have changed in this diff Show more