diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index d52c8382e9..cf01c91626 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit d52c8382e9e94b2b44ceecd7cb7bd897e091073c +Subproject commit cf01c91626bf58bdf6895c59a706b83c37a9e345 diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 075ca14999..42ef53fa15 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -19,8 +19,8 @@ import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_IDS import com.tangem.domain.models.scan.CardDTO.Companion.RING_BATCH_PREFIX import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletBuilder -import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.builder.UserWalletIdBuilder +import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.Artwork import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.onboarding.data.model.CreateWalletResponse @@ -641,6 +641,8 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) ) } } else { + delay(HIDE_PROGRESS_DELAY) + readCard { newScanResponse -> scanResponse = newScanResponse userWallet = createUserWallet(newScanResponse, backupState) @@ -658,7 +660,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) // All cardIds may already be activated if the backup was skipped before. if (notActivatedCardIds.isEmpty()) { delay(1000) - store.dispatch(BackupAction.BackupFinished(userWallet?.walletId)) + store.dispatchWithMain(BackupAction.BackupFinished(userWallet?.walletId)) return@launch } @@ -667,7 +669,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds) handleFinishBackup(requireNotNull(scanResponse)) delay(1000) - store.dispatch(BackupAction.BackupFinished(userWalletId = userWallet?.walletId)) + store.dispatchWithMain(BackupAction.BackupFinished(userWalletId = userWallet?.walletId)) } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 07d83bda21..b80aa53ebb 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.core.view.MenuProvider import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.transition.TransitionManager import by.kirich1409.viewbindingdelegate.viewBinding @@ -574,22 +575,28 @@ class OnboardingWalletFragment : } animator.showSuccess { - flCardsContainer.hide() - imvCardBackground.hide() - showConfetti(true) - imvSuccess.alpha = 0f - imvSuccess.show() - imvSuccess.animate()?.alpha(1f)?.duration = 400 + if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { + flCardsContainer.hide() + imvCardBackground.hide() + showConfetti(true) + imvSuccess.alpha = 0f + imvSuccess.show() + imvSuccess.animate()?.alpha(1f)?.duration = 400 + } } } - internal fun showConfetti(show: Boolean) = with(binding.vConfetti) { - lavConfetti.show(show) + internal fun showConfetti(show: Boolean) { + if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { + with(binding.vConfetti) { + lavConfetti.show(show) - if (show) { - lavConfetti.playAnimation() - } else { - lavConfetti.cancelAnimation() + if (show) { + lavConfetti.playAnimation() + } else { + lavConfetti.cancelAnimation() + } + } } } diff --git a/app/src/main/res/layout/item_backup_info_adapter.xml b/app/src/main/res/layout/item_backup_info_adapter.xml index 469434318b..46b359a8f1 100644 --- a/app/src/main/res/layout/item_backup_info_adapter.xml +++ b/app/src/main/res/layout/item_backup_info_adapter.xml @@ -23,9 +23,9 @@ android:id="@+id/tv_body" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="64dp" + android:layout_marginStart="32dp" android:layout_marginTop="10dp" - android:layout_marginEnd="64dp" + android:layout_marginEnd="32dp" android:textColor="@color/text_secondary" android:textAlignment="center" tools:text="Body" diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt index 52398bd178..5d8b37fd46 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt @@ -130,7 +130,7 @@ private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) } SpacerH16() FooterContainer( - footer = stringResource(id = R.string.give_permission_fee_footer), + footer = data.footerText.resolveReference(), modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { FeeItem(fee = data.fee) @@ -321,6 +321,7 @@ private val previewData = GiveTxPermissionBottomSheetConfig( onChangeApproveType = { ApproveType.LIMITED }, subtitle = resourceReference(R.string.give_permission_staking_subtitle, wrappedList("1")), dialogText = resourceReference(R.string.give_permission_staking_footer), + footerText = resourceReference(R.string.swap_give_permission_fee_footer), ), onCancel = {}, ) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt index 9f52bca622..b88bfa47d4 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/state/GiveTxPermissionState.kt @@ -15,6 +15,7 @@ sealed class GiveTxPermissionState { data class ReadyForRequest( val subtitle: TextReference, val dialogText: TextReference, + val footerText: TextReference, val currency: String, val amount: String, val walletAddress: String, diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 94637867c0..9600cda393 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -178,6 +178,7 @@ sealed class AnalyticsParam { const val ERROR_DESCRIPTION = "Error Description" const val ERROR_CODE = "Error Code" const val ERROR_KEY = "Error Key" + const val ERROR_TYPE = "Error Type" const val CREATION_TYPE = "Creation type" const val DAPP_NAME = "DApp Name" const val DAPP_URL = "DApp Url" diff --git a/core/datasource/build.gradle.kts b/core/datasource/build.gradle.kts index 0a236d6e5a..494f2c12e6 100644 --- a/core/datasource/build.gradle.kts +++ b/core/datasource/build.gradle.kts @@ -50,7 +50,6 @@ dependencies { implementation(deps.okHttp.prettyLogging) implementation(deps.retrofit) implementation(deps.retrofit.moshi) - implementation(deps.reactive.network) /** Time */ implementation(deps.jodatime) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt index 7682c70d4f..a56308d255 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt @@ -64,6 +64,9 @@ enum class ExchangeStatus { @Json(name = "tx-failed") TxFailed, + @Json(name = "paused") + Paused, + @Json(name = "unknown") Unknown, } diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt new file mode 100644 index 0000000000..3e91a7016f --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/connection/AndroidNetworkConnectionManager.kt @@ -0,0 +1,26 @@ +package com.tangem.datasource.connection + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import androidx.core.content.ContextCompat + +internal class AndroidNetworkConnectionManager( + private val applicationContext: Context, +) : NetworkConnectionManager { + + override val isOnline: Boolean + get() = isNetworkAvailable() + + private fun isNetworkAvailable(): Boolean { + val connectivityManager = ContextCompat.getSystemService(applicationContext, ConnectivityManager::class.java) + + // Network that is currently in use (several networks can be presented at the same time) + val defaultNetwork: Network = connectivityManager?.activeNetwork ?: return false + val networkCapabilities: NetworkCapabilities = + connectivityManager.getNetworkCapabilities(defaultNetwork) ?: return false + + return networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/NetworkConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/NetworkConnectionManager.kt index dc1c3e9051..fa6faca57d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/connection/NetworkConnectionManager.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/connection/NetworkConnectionManager.kt @@ -1,13 +1,8 @@ package com.tangem.datasource.connection -import kotlinx.coroutines.flow.StateFlow - /** Network connection manager */ interface NetworkConnectionManager { /** Connection status */ val isOnline: Boolean - - /** Connection status flow */ - val isOnlineFlow: StateFlow } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt b/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt deleted file mode 100644 index 7a3279d188..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/connection/RealInternetConnectionManager.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.tangem.datasource.connection - -import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork -import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings -import com.tangem.utils.coroutines.FeatureCoroutineExceptionHandler -import io.reactivex.BackpressureStrategy -import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.reactive.asFlow - -private const val PING_SERVER = "https://clients3.google.com/generate_204" -private const val PING_INTERVAL = 5_000 - -internal class RealInternetConnectionManager : NetworkConnectionManager { - - private val scope = - CoroutineScope( - SupervisorJob() + Dispatchers.IO + FeatureCoroutineExceptionHandler.create("RealInternetConnectionManager"), - ) - - private val initialNetworkResult: Boolean by lazy { - ReactiveNetwork - .checkInternetConnectivity( - InternetObservingSettings.builder() - .host(PING_SERVER) - .port(443) - .build(), - ) - .subscribeOn(Schedulers.io()) - .observeOn(Schedulers.io()) - .blockingGet() - } - - override val isOnlineFlow: StateFlow = ReactiveNetwork - .observeInternetConnectivity( - InternetObservingSettings.builder() - .host(PING_SERVER) - .port(443) - .interval(PING_INTERVAL) - .build(), - ) - .toFlowable(BackpressureStrategy.LATEST) - .asFlow() - .stateIn(scope, SharingStarted.Lazily, initialValue = initialNetworkResult) - - override val isOnline: Boolean - get() = isOnlineFlow.value -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt index b3eca89707..f698c5144d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkConnectionModule.kt @@ -1,10 +1,12 @@ package com.tangem.datasource.di +import android.content.Context +import com.tangem.datasource.connection.AndroidNetworkConnectionManager import com.tangem.datasource.connection.NetworkConnectionManager -import com.tangem.datasource.connection.RealInternetConnectionManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @@ -14,7 +16,7 @@ internal class NetworkConnectionModule { @Provides @Singleton - fun provideNetworkConnectionManager(): NetworkConnectionManager { - return RealInternetConnectionManager() + fun provideNetworkConnectionManager(@ApplicationContext applicationContext: Context): NetworkConnectionManager { + return AndroidNetworkConnectionManager(applicationContext = applicationContext) } } \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 12b6f28389..9fa2f27509 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -20,7 +20,7 @@ Gehe zu den Einstellungen, um die biometrische Authentifizierung in der Tangem-App zu aktivieren Biometrische Authentifizierung aktivieren Dadurch werden alle gespeicherten Zugangscodes für die Wallet gelöscht. Jede weitere Operation mit der Wallet erfordert die Übermittlung des Zugangscodes. - Durch das Entfernen der gespeicherten Karte oder Ring werden alle gespeicherten Wallets und deren Zugangscodes aus der App gelöscht. + Durch das Entfernen der gespeicherten Geräte werden alle gespeicherten Wallets und deren Zugangscodes aus der App gelöscht. Zugangscode speichern Bei Interaktionen mit deiner Karte oder Ring wird anstelle des Zugangscodes eine biometrische Authentifizierung abgefragt. Behalte die Wallet in der App @@ -35,14 +35,14 @@ Verstanden Guthaben sind ausgeblendet Bitte Karte oder Ring scannen - Bitte versuche es in 30 Sekunden erneut oder scanne die Karte + Bitte versuche es in 30 Sekunden erneut oder scanne die Karte oder Ring Zu viele Versuche Du hast die biometrische Authentifizierung auf deinem Telefon deaktiviert und kannst keine Wallets in der App speichern. Um Wallets zu speichern, aktiviere bitte die biometrische Authentifizierung in deinen Telefoneinstellungen. Backup-Prozess starten Verwende eine Bankkarte oder eine andere Zahlungsmethode - %d Karte oder Ring - %d Karten oder Ringe + %d Gerät + %d Geräte Deaktiviere diese Option, wenn du nicht möchtest, dass diese Karte oder Ring zum Zurücksetzen von Zugangscodes auf anderen Karten oder Ringe in dieser Wallets verwendet wird. Bitte beachte, dass du dann auch den Zugangscode auf dieser Karte oder Ring nicht zurücksetzen kannst. Ermöglicht die Verwendung dieser Karte oder Ring zum Zurücksetzen des Zugangscodes auf anderen Karten oder Ringe in dieser Brieftasche @@ -51,15 +51,15 @@ Möchtest du das wirklich tun? Zugangscode ändern Der Zugangscode wird nur auf dieser Karte oder Ring geändert - Alle Karten oder Ringe der ausgewählten Wallet wurden auf die Werkseinstellungen zurückgesetzt. Du kannst nun eine neue Wallet aufsetzen. + Alle Tangem Geräte der ausgewählten Wallet wurden auf die Werkseinstellungen zurückgesetzt. Du kannst nun eine neue Wallet aufsetzen. Komplett zurücksetzen - Möchtest du die nächste Karte oder Ring in dieser Wallet zurücksetzen? + Möchtest du die nächsten geräte in dieser Wallet zurücksetzen? Karte oder Ring zurücksetzen - Wir empfehlen dir, den Zurücksetzungsprozess für alle Karten oder Ringe in dieser Wallet abzuschließen - Du hast nicht alle deine Karten oder Ringe zurückgesetzt + Wir empfehlen dir, den Zurücksetzungsprozess für aller Geräte in dieser Wallet abzuschließen + Du hast nicht alle deine Geräte zurückgesetzt Auf Werkseinstellungen zurücksetzen Sicherheitsmodus - Karteneinstellungen + Geräteeinstellungen Zusätzlich zu den Netzwerkgebühren erhebt das Cardano-Netzwerk %1$s ADA bei Transaktionen mit dem Token %2$s Cardano-Transaktionsanforderungen Um eine %1$s Transaktion durchzuführen, musst du einen gewissen ADA-Betrag einzahlen, um die Netzwerkgebühr und den ADA-Mindestwert zu decken (5 ADA werden empfohlen). @@ -83,10 +83,12 @@ Gehe zu %1$s D hast keinen Zugang zur Kamera erteilt, bitte passe deine Datenschutzeinstellungen an Abbrechen + Ändern Aktion wählen Beanspruchen Stakingbelohnungen beanstpruchen Schließen + Bestätigen Weiter Kopieren Adresse kopieren @@ -119,14 +121,14 @@ Adressen abrufen Zum Anbieter gehen Zum Token - Stunde + stunde Importieren In Arbeit Später %1$s übrig Gesperrt Hauptnetz - Monat + monat Netzgebühr Der überwiesene Betrag wird um %1$s (%2$s) gekürzt, um die gewählte Gebührenhöhe zu decken. Weiter @@ -176,7 +178,7 @@ Es ist ein Fehler aufgetreten. Bitte versuche es erneut. Nicht erreichbar staking beenden - Woche + woche Ja Vertragsadresse kopiert! Verfügbare Netzwerke @@ -213,13 +215,13 @@ Zugangscode Du musst den richtigen Zugangscode eingeben, bevor du die Karte oder Ring scannst. Langes Tippen - Dieser Mechanismus schützt vor Annäherungsangriffen auf eine Karte oder Ring. Es wird eine Verzögerung zwischen dem Empfang und der Ausführung eines Befehls erzwungen. Nach der ersten signierten Transaktion wird dieses Telefon mit der Karte oder Ring verknüpft und die Transaktionen werden sofort signiert. + Dieser Mechanismus schützt vor Annäherungsangriffen auf eine Karte oder einen Ring. Er erzwingt eine Verzögerung zwischen dem Empfang und der Ausführung eines Befehls. Passcode Bevor du einen Befehl ausführst, der eine Änderung des Kartenstatus zur Folge hat, musst du den Passcode eingeben. Empfehlungsprogramm Flippe den Bildschirm deines Geräts nach unten, um Salden schnell ein- und auszublenden %s Hasch - Karten- oder Ring-ID + Geräte-ID Kontakt zum Support Weitere Karten oder Ringe verknüpfen App Währung @@ -257,6 +259,7 @@ Tauschen läuft Austauschen... Fehlgeschlagen + Pausiert Einzahlung empfangen Einzahlung wird erwartet Warten auf Einzahlung... @@ -297,17 +300,16 @@ Eine Transaktion kann nicht gesendet werden Fehler in der Coinbeschreibung Nur diese - Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Sie die Verwendung Ihres Tokens für den Swap genehmigen. Gib das Genehmigungslimit für das ausgewählte Token an Betrag %s Die Genehmigungsfunktion ist erforderlich, um einer anderen Adresse die Berechtigung zur Verwendung einer bestimmten Menge Ihrer Token zu erteilen. Standardmäßig können Smart Contracts nicht auf deine Token zugreifen, es sei denn, du stimmen zu. Indem du deine Token \"freischaltest\", autorisierst du den StakeKit Smart Contract, sie zu verwenden. Die Miner des Netzwerks erhalten eine Gasgebühr (von dir bezahlt), um diese Aktion in der Blockchain aufzuzeichnen. Du kannst deine Token einsetzen, nachdem du die Genehmigung erteilt hast. - Um fortzufahren, musst du StakeKit Smart Contract erlauben, deine %s zu verwenden + Um fortzufahren, musst du Polygon Smart Contract erlauben, deine %s zu verwenden Um fortzufahren, erteile %1s Smart Contracts die Berechtigung, dein zu %2s verwenden. Erlaubnis erteilen Unbegrenzt Karte oder Ring bestellen Karte oder Ring scannen - Diese Information ist KI generiert + Diese Informationen wurden mithilfe von KI generiert.\nTippe hier, wenn Du Fehler finden. Um den Zugangscode zu ändern, halte die Karte oder Ring wie oben gezeigt an das Gerät und entferne sie erst am Ende des Vorgangs. Um den Passcode zu ändern, halte die Karte oder Ring wie oben gezeigt an das Gerät und entferne sie erst am Ende des Vorgangs. Um die Wallet zu erstellen, halte die Karte oder Ring wie oben gezeigt an das Gerät und entferne sie erst am Ende des Vorgangs. @@ -390,8 +392,8 @@ Beliebt Über %s - %d Börse - %d Börsen + %d börse + %d börsen Bewertung, basierend auf %d @@ -427,7 +429,6 @@ Die Änderung der Liquidität, die dem Token während des angegebenen Zeitraums zur Verfügung steht Liquidität Liquiditätsindex - Leer Gelistet auf Niedrig MarketCap @@ -455,11 +456,11 @@ Rufe dies auf oder tippe auf die Suchleiste, um Token direkt vom Markt hinzuzufügen Token hinzufügen NFC ist auf deinem Gerät nicht verfügbar - Du musst einen einzigen Zugangscode einrichten, um alle deine Karten oder Ringe zu schützen + Du musst einen einzigen Zugangscode einrichten, um alle deine Geräte zu schützen Schützen Du kannst später auf jeder Karte oder Ring einen individuellen Zugangscode einrichten Personalisieren - Der Zugangscode kann mit einer verbundenen Karte oder Ring wiederhergestellt werden. Bewahre nicht alle Karten oder Ringe an einem Ort auf. + Stelle den Zugangscode mit einem verknüpften Gerät wieder her. Bewahre nicht alle Deine Geräte an einem Ort auf. Wiederherstellen Wähle ein beliebiges Wort, eine Phrase oder eine Zahl als Zugangscode Zugangscode erstellen @@ -470,7 +471,7 @@ Bitte wiederhole den Vorgang. Die Karte oder Ring wird auf Werkseinstellungen zurückgesetzt. Aktivierungsfehler Token hinzufügen - Du hast eine Backup-Karte oder Ring hinzugefügt. Wenn der Backup-Prozess abgeschlossen ist, kannst du keine weiteren Backup-Karten oder Ringe hinzufügen. Wenn du noch eine Karte oder Ring hast, füge diese zum Backup hinzu. Möchtest du den Backup-Prozess fortsetzen? + Du hast einee Backup-Karte oder einen Backup-Ring hinzugefügt. Wenn der Backup-Prozess abgeschlossen ist, kannst Du keine weiteren Backup-Geräte hinzufügen. Wenn Du noch eine Karte oder einen Ring hast, fügen diese(n) zum Backup hinzu. Möchtest Du den Backup-Prozess fortsetzen? Der Sicherungsvorgang ist teilweise abgeschlossen. Du kannst ihn jetzt nicht beenden. Die Passphrase ist eine fortschrittliche Sicherheitsfunktion, die von Krypto-Wallets verwendet wird. Sie fügt ein zusätzliches Wort oder eine Phrase deiner Wahl zu der bereits bestehenden Wiederherstellungsphrase hinzu, um einen brandneuen Satz von Adressen zu erzeugen. Hinzufügen einer Sicherungskarte oder Ring @@ -525,16 +526,16 @@ Bereite die primäre Karte oder Ring mit der Nummer %s vor. Bereite den Ring vor und drücke unten die Scan-Taste Deine Tangem-Karte oder Ring ist konfiguriert und einsatzbereit. - Maximale Anzahl an Karten oder Ringe hinzugefügt. Schließe den Sicherungsvorgang ab. + Maximale Anzahl an Geräten hinzugefügt. Schließe den Sicherungsvorgang ab. Karte oder Ring aktivieren Sicherungs-Karte oder Ring Sicherungskarte oder Ring Nr. %d Sicherungs-Ring - Keine Sicherungskarten oder Sicherungsring + Keine Sicherungsgeräte Benachrichtigungen - Eine Backup-Karte oder Ring hinzugefügt + Eine Backupgerät hinzugefügt Bereite deine Karte oder Ring vor - Zwei Backup-Karten oder Ring hinzugefügt + Zweis Backupgerät hinzugefügt Um zu beginnen, lade einfach die wallet mit einem beliebigen Betrag auf Um zu beginnen, lade die Wallet einfach mit mehr als %1$s %2$s auf. Krypto kaufen @@ -550,6 +551,9 @@ Zugangscode wiederherstellen Identische Karten oder Ring Zugangscode + Unsere Dienstleistungen sind in diesem Land nicht verfügbar + Änder oder bestätige bitte + Dein Wohnsitz wurde identifiziert als Gruppe erstellen Nach Guthaben Token organisieren @@ -609,8 +613,8 @@ Möchtest du Biometrie nutzen? Beachte, dass für Transaktionen mit deinem Guthaben weiterhin deine Karte oder Ring erforderlich ist Karte oder Ring scannen - Scanne die Karte oder Ring, um ihre Einstellungen zu ändern. Die Änderungen wirken sich nur auf die von dir gescannte Karte oder Ring aus und haben keine Auswirkungen auf andere mit deiner Wallet verknüpften Karten oder Ringe. - Halte deine Karte bereit! + Scanne die Karte oder Ring, um ihre Einstellungen zu ändern. Die Änderungen wirken sich nur auf die von dir gescannte Karte oder Ring aus und haben keine Auswirkungen auf andere mit deiner Wallet verknüpften Geräte. + Halte deine Karte oder Ring bereit! Bereits in der eingegebenen Adresse enthalten Der Provisionsbetrag ist %s mal der empfohlene Betrag. Stelle sicher, dass die benutzerdefinierten Einstellungen korrekt sind. Du hast eine Kommission angegeben, die unter dem empfohlenen Betrag liegt, was zu einer Verzögerung Ihrer Transaktion führen könnte. Weiter? @@ -710,13 +714,14 @@ Belohnungen beanspruchen Eine Möglichkeit, Staking-Belohnungen zu erhalten. Es kann automatisch oder manuell beansprucht werden. Belohnungszeitplan - Dabei handelt es sich um einen Zeitplan, der festlegt, wann die Teilnehmer am Staking ihre Belohnungen erhalten. + Dies ist ein Zeitplan, der bestimmt, wann Teilnehmer am Staking ihre Belohnungen erhalten. Der Zeitpunkt der Belohnungsverteilung kann je nach Validator und Netzwerklast leicht variieren. Belohnungen %s Staking %s Entbindungsdauer Der Zeitraum, den du nach der Beantragung der Abhebung von Geldern aus dem Staking warten musst, bevor die Token verfügbar werden. Aufwärmphase Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking. + Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Du die Verwendung Deines Tokens für das Staking genehmigst. Indem Du die Staking-Funktionalität nutzt, stimmst Du den %1$s und %2$s des Anbieters zu. Gesperrt Migrieren @@ -736,6 +741,7 @@ Aufgrund von Netzwerkproblemen ist Staking derzeit nicht verfügbar. Bitte versuche es später erneut. Beim Staking im %1$s -Netzwerk mit einem neuen Validator werden alle zuvor eingesetzten Kryptos automatisch an diesen Validator übertragen Reinvestiert Deine verdienten Prämien in Deinen Einsatzbetrag und erhöht so den potenziellen Gewinn. + Mit Restake kannst Du Dein Guthaben von einem Validator zu einem anderen verschieben, ohne dass Du den Stake aufheben musst. Entsperre dein Geld, um es aus dem Staking-Prozess abzuheben. Das Freischalten nimmt %s. Nach Ablauf der 21-tägigen Bindungsfrist kannst Du über Dein Guthaben verfügen. Die Prämie wird zusammen mit dem ungestaketen Guthaben abgehoben. Deine Assets steht Dir nach Ablauf der Frist für die Aufhebung der Bindung %s zur Verfügung. @@ -754,6 +760,8 @@ Täglich Alle %s Täglich + Alle %s + Jeder Pro Epoche Pro Ära Stündlich @@ -762,7 +770,7 @@ Belohnungen Stake gesperrt Mehr staken - Du setzt %1$s ein und erhältst jährlich %2$s + Du setzt %1$s ein und ethältst Deine Belohnung %2$s Zum Entsperren antippen Tippe zum Entsperren oder Abstimmen Zum abheben antippen @@ -772,6 +780,7 @@ Lösen der Bindungen Gelocktes unlocken Entsperren + Der Betrag übersteigt das eingesetzte Guthaben Unstaken Staking beenden Unstake Assets %s @@ -793,6 +802,7 @@ Lerne Tangem kennen Tausche, kaufen Sie NFTs, vergebe Kredite und tätige Einlagen bei mehr als 100 verschiedenen dezentralen Diensten Web 3.0-kompatibel + Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Sie die Verwendung Ihres Tokens für den Swap genehmigen. Tausche mehr Token zu besseren Kursen direkt in deiner Brieftasche. Neuer Swap-Anbieter verfügbar! Der Betrag umfasst:\n- Gebühr des Dienstanbieters\n- Netzgebühr für die Rücksendung von %s von der Vermittlungsstelle an die Adresse des Nutzers. @@ -879,6 +889,7 @@ Antrag auf Erstellung einer Transaktion für %1$s\n%2$s\n\nBetrag: %3$s\nGebühr: %4$s\nGesamt: %5$s\nSaldo: %6$s Die Transaktion kann nicht gesendet werden. Nicht genügend Guthaben. WalletConnect Sitzung konnte nicht aufgebaut werden. Bitte versuche es später noch einmal. + Nicht alle Token wurden zu Deiner Liste hinzugefügt. Bitte füge sie zuerst hinzu und versuche es erneut. Fehlende Token: \n Nachricht konnte nicht signiert werden.\nBitte, versuche es erneut WalletConnect-Sitzung konnte nicht aufgebaut werden: Timeout-Fehler. Bitte versuche es später noch einmal. Sitzungsanfrage enthält nicht unterstützte Blockchains für WalletConnect-Verbindung. Nicht unterstützte Blockchains:\n @@ -955,7 +966,7 @@ Auf dieser Karte oder Ring sind nur noch %s Unterschriften übrig. Du musst dein gesamtes Guthaben abheben. Geringe Anzahl von Unterschriften Token in verschiedenen Netzwerken können unterschiedliche Adressen haben. Überprüfe bei der Überweisung noch einmal, ob deine Adresse mit der des Netzwerks übereinstimmt. - MATIC wird auf POL migriert. Es gibt jedoch keine Frist, und MATIC wird noch nicht abgeschafft. Du kannst MATIC-Token weiterhin verwenden oder sie über eien Exchange gegen POL tauschen. + MATIC wird zu POL migriert. Es gibt jedoch keine festgelegte Frist und MATIC wird noch nicht abgeschafft. Du kannst den MATIC-Token weiterhin bedenkenlos verwenden oder es gegen POL austauschen. Migration von MATIC zu POL Verwende deine Karte oder Ring, um eine Adresse für das %d-Netz zu erhalten @@ -986,7 +997,7 @@ Du hast eine unterbrochene Sicherung. Möchtest du diese fortsetzen? Ja, fortsetzen Verwerfen - Wenn du das Backup jetzt abbrichst, musst du die Karten oder Ringe auf die Werkseinstellungen zurücksetzen, um von vorne zu beginnen + Wenn du das Backup jetzt abbrichst, musst du die Geräte auf die Werkseinstellungen zurücksetzen, um von vorne zu beginnen Sicherung fortsetzen Dies ist eine unwiderrufliche Aktion Anmelden mit %s diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index a0dc037e9d..6703130663 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -83,10 +83,12 @@ Ir a %1$s No ha otorgado acceso a su cámara, cambie su configuración de privacidad Cancelar + Cambie Elige una acción Reclamar Reclame recompensas Cerrar + confirme Continuar Copiar Copiar la dirección @@ -97,6 +99,10 @@ %d día %d días + + día + días + Suprimir Desactivado Listo @@ -115,12 +121,14 @@ Obtener direcciones Ir al proveedor Ir al token + hora Importe En progreso Más tarde %1$s quedan Bloqueado Red principal + mes Tarifa de la red La cantidad enviada se reducirá en %1$s (%2$s) para cubrir el nivel de tarifa seleccionado Siguiente @@ -142,8 +150,9 @@ Renombrar Guarde Guardar cambios - Buscar en el mercado + Buscar Buscar tokens + sec Seed phrase Seleccione una acción Vender @@ -169,6 +178,7 @@ Hubo un error. Por favor inténtelo de nuevo. Inaccesible Termine el staking + semana Dirección del contract copiada Redes disponibles @@ -249,6 +259,7 @@ En intercambio En intercambio Fallido + En pausa Depósito recibido Esperando depósito Esperando depósito @@ -287,18 +298,18 @@ Comentarios Comentarios en Tangem No se puede completar una transacción + Error de descripción de moneda Esta transacción - La red cobrará una tarifa de aprobación de token para verificar que está autorizando el uso de su token para el swap. Especifique el límite aprobado para el token seleccionado. Montante %s La función Aprobar es necesaria para otorgar permiso a otra dirección para usar una cantidad específica de sus tokens. Por diseño, los contratos inteligentes no pueden acceder a sus tokens sin su aprobación. Al \"desbloquear\" sus tokens, autoriza al contrato inteligente de StakeKit a usarlos. Los mineros de la red reciben una tarifa de gas (pagada por usted) por registrar esta acción en la cadena de bloques. Puede apostar su token después de dar su aprobación. - Para continuar, debe autorizar el contrato inteligente de StakeKit para utilizar su %s + Para continuar, debe autorizar el contrato inteligente de Polygon para utilizar su %s Para continuar, de autorización a los smart contracts de %1s para usar su %2s Dar autorización Ilimitado Ordenar Escanee - Esta información fue generada con IA + Esta información fue generada con IA.\nPulse aquí si encuentra algún error. Toque para cambiar la contraseña Toque para cambiar la contraseña Para crear la billetera, toque la tarjeta como se muestra arriba y no la retire hasta el final de la operación @@ -347,6 +358,7 @@ La billetera no admite más de una red Para comprar, intercambiar o recibir este activo, agréguelo a su portafolio Este activo no está disponible en la billetera + Este activo no está disponible para esta billetera Añadir al portafolio Agregue un token Redes disponibles @@ -379,6 +391,10 @@ Top Perdedores Tendencias Acerca de %s + + %d intercambio + %d intercambios + Basado en %d evaluación Basado en %d evaluaciónes @@ -390,6 +406,12 @@ Suministro circulante El número total de monedas que están disponibles para el comercio y que circulan en el mercado. Suministro circulante + No se encontraron intercambios + Intercambio + Precaución + Arriesgado + Confiable + Intercambios Compradores experimentados Compradores netos con el requisito adicional de tener al menos 100 transacciones salientes Compradores experimentados @@ -407,6 +429,7 @@ El cambio en la cantidad de liquidez disponible para el token durante el período de tiempo especificado Liquidez Índice de liquidez + Listado en Bajo Capital. de mercado El valor total de una criptomoneda calculado multiplicando su precio por la cantidad de monedas en circulación @@ -429,6 +452,7 @@ Volumen de operaciones (24 horas) La cantidad total de una criptomoneda que se ha negociado en las últimas 24 horas, lo que indica el nivel de actividad y liquidez en el mercado. Volumen de operaciones (24 horas) + Volumen Tire hacia arriba o toque la barra de búsqueda para agregar tokens directamente desde el mercado Agregar tokens NFC no está disponible en su dispositivo @@ -477,12 +501,12 @@ Creando un backup Leer más sobre seed phrase - Vacío + Escriba estas %d palabras en el orden que se indica a continuación y guárdelas en un lugar seguro y secreto. Su seed phrase - Vacío + %d palabras Para importar su billetera, ingrese su seed phrase en el campo de abajo @@ -527,6 +551,9 @@ Restaurar código de acceso Tarjetas idénticas Código de acceso + Nuestros servicios no están disponibles en este país + Cámbielo o confírmelo + Su residencia ha sido identificada como Agrupar Por saldo Organizar tokens @@ -687,13 +714,14 @@ Reclamo de recompensa Método para recibir recompensas por apuesta.\nPuede ser automático, donde la recompensa se acredita en tu dirección, o manual, donde debes retirar la recompensa creando una transacción para recibirla. Calendario de recompensas - Este es un cronograma que determina cuándo los participantes reciben sus recompensas. + Este es un cronograma que determina cuándo los participantes reciben sus recompensas. La duración de la distribución de recompensas puede variar ligeramente según el validador y la carga de la red. Recompensas para reclamar: %s Staking %s Periodo de disociación El período que debes esperar después de solicitar el retiro de fondos del staking antes de que los tokens estén disponibles. Periodo de calentamiento El tiempo permitido para activar la participación en la apuesta. + La red cobrará una tarifa de aprobación de token para verificar que usted está autorizando el uso de su token para el staking. Al utilizar la función de staking, usted acepta %1$s y %2$s del proveedor Bloqueado Migrar @@ -713,6 +741,7 @@ El staking no está disponible actualmente debido a las condiciones de la red. Inténtelo de nuevo más tarde. El staking en la red %1$s con un nuevo validador transferirá automáticamente todos los fondos previamente en staking a este validador. Reinvierta las recompensas obtenidas en el monto apostado, aumentando las ganancias potenciales. + La opción de volver a staking le permite mover sus fondos de un validador a otro sin necesidad de retirarlos. Desbloquee su dinero para retirarlo del proceso de staking. El desbloqueo demora %s minuto. Sus fondos estarán disponibles para su uso después del período de desvinculación de 21 días. La recompensa se retirará junto con los fondos de desvinculación. Sus fondos estarán disponibles para su uso después del período de desvinculación de %s. @@ -729,7 +758,10 @@ Manual Bloquear Día + Cada %s Cada día + Cada %s + Cada Época Era Hora @@ -738,8 +770,9 @@ Recompensas El stake está bloqueado Hacer más staking - Haga staking de %1$s y recibirá %2$s anualmente + Ud hace el staking de %1$s y recibirá %2$s Toque para desbloquear + Pulse para desbloquear o votar Toque para retirar Stake %s El unstaking de %s @@ -747,6 +780,7 @@ Desunión Desbloquear Desbloqueando + El monto excede el saldo apostado Sin staking Unstaking Detenando el staking de activos %s @@ -768,6 +802,7 @@ Descubra Tangem Intercambie, compre NFT, haga préstamos y depósitos en más de 100 servicios descentralizados diferentes Compatible con Web 3.0 + La red cobrará una tarifa de aprobación de token para verificar que está autorizando el uso de su token para el swap. Intercambie más tokens a mejores tasas directamente en su billetera. ¡Nuevo proveedor de intercambio disponible! El monto incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario. @@ -818,6 +853,7 @@ Operación desde: %s a: %s + validador: %s Inténtelo de nuevo Ha escaneado la misma tarjeta. Para crear una billetera gemela, necesite escanear la tarjeta con no. %d Ha escaneado la tarjeta gemela incorrecta. Por favor, intente con otra @@ -853,6 +889,7 @@ Solicitud para crear una transacción para %1$s\n%2$s\n\nMontante: %3$s\nComisión: %4$s\nTotal: %5$s\nSaldo: %6$s No se puede completar la transacción. Fondos insuficientes. Error al establecer la sesión de WalletConnect. Por favor, inténtelo más tarde. + No todos los tokens se han agregado a su lista. Añádelos primero y vuelve a intentarlo. Tokens faltantes :\n Error al firmar el mensaje.\nPor favor, inténtalo de nuevo Error al establecer la sesión de WalletConnect: error de tiempo de espera. Por favor, inténtelo más tarde. La solicitud de sesión contiene blockchains no compatibles para la conexión WalletConnect. Blockchains no compatibles:\n diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 7a6b959868..80f7d2e337 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -83,10 +83,12 @@ Aller à %1$s Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité Annuler + Changez Choisissez une action Réclamer Réclamez des récompenses Fermer + Confirmez Continuer Copier Copier l\'adresse @@ -97,6 +99,10 @@ %d jour %d jours + + jour + jours + Supprimer Désactivé Exécuté @@ -115,12 +121,14 @@ Obtenir des adresses Aller au fournisseur Aller au jeton + heure Importez En cours Plus tard Il reste %1$s Verrouillé Réseau principal + mois Commissions du réseau Le montant envoyé sera réduit de %1$s(%2$s) pour couvrir le niveau de frais sélectionné Suivant @@ -144,6 +152,7 @@ Sauvegarder les modifications Rechercher Rechercher des jetons + sec Seed phrase Sélectionnez une action Vendre @@ -169,6 +178,7 @@ Il y avait une erreur. Veuillez réessayer. Inaccessible Unstakez + semaine Oui Adresse du contrat copiée ! Réseaux disponibles @@ -249,6 +259,7 @@ En cours d\'échange En cours d\'échange Échoué + En pause Dépôt reçu En attente du dépôt En attente du dépôt @@ -287,18 +298,18 @@ Commentaires Commentaires sur Tangem Impossible d\'effectuer une transaction + Erreur de description de la pièce Cette transaction - Le réseau facturera des frais d\'approbation de jeton pour vérifier que vous autorisez l\'utilisation de votre jeton pour l\'échange. Spécifiez la limite approuvée pour le jeton sélectionné Montant %s La fonction Approuver est nécessaire pour accorder la permission à une autre adresse d\'utiliser une quantité spécifique de vos jetons. De par leur conception, les contrats intelligents ne peuvent pas accéder à vos jetons sans votre approbation. En « déverrouillant » vos jetons, vous autorisez le contrat intelligent StakeKit à les utiliser. Les mineurs du réseau reçoivent des frais de gaz (payés par vous) pour enregistrer cette action sur la blockchain. Vous pouvez staker votre jeton après avoir donné votre approbation. - Pour continuer, vous devez autoriser le contrat intelligent StakeKit à utiliser votre %s + Pour continuer, vous devez autoriser le contrat intelligent Polygon à utiliser votre %s Pour continuer, accordez aux smart contracts de %1s l\'autorisation d\'utiliser votre %2s Donner l\'autorisation Illimité Commandez Scannez - Ces informations ont été générées avec l\'IA + Ces informations ont été générées avec l\'IA.\nAppuyez ici si vous trouvez des erreurs. Touchez, pour modifier le code d\'accès Touchez, pour modifier le mot de passe 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 @@ -347,6 +358,7 @@ Le portefeuille ne prend pas en charge plus d\'un réseau Pour acheter, échanger ou recevoir cet actif, ajoutez-le à votre portefeuille Cet actif n\'est plus pris en charge dans le portefeuille + Cet actif n\'est pas disponible pour ce portefeuille Ajouter au portfolio Ajoutez un jeton Réseaux disponibles @@ -379,6 +391,10 @@ Top Perdants Tendances À propos de %s + + %d échange + %d échanges + Basé sur %d évaluation Basé sur %d évaluations @@ -390,6 +406,12 @@ Approvisionnement en circulation Le nombre total de pièces disponibles pour le trading et en circulation sur le marché Approvisionnement en circulation + Aucun échange trouvé + Échange + Précaution + Risqué + Fiable + Échanges Acheteurs expérimentés Acheteurs nets avec l\'exigence supplémentaire d\'avoir au moins 100 transactions sortantes Acheteurs expérimentés @@ -407,6 +429,7 @@ Le changement dans la quantité de liquidité disponible pour le jeton pendant la période spécifiée Liquidité Indice de liquidité + Listé sur Faible Cap. boursière La valeur d\'une crypto-monnaie est calculée en multipliant son prix par le nombre de pièces en circulation @@ -429,6 +452,7 @@ Volume des échanges (24h) Le montant total d\'une crypto-monnaie qui a été échangé au cours des dernières 24 heures, indiquant le niveau d\'activité et de liquidité du marché Volume des échanges (24h) + Volume Tirez vers le haut ou appuyez sur la barre de recherche pour ajouter des jetons directement depuis le marché Ajouter des jetons NFC n\'est pas disponible sur votre appareil @@ -477,12 +501,12 @@ Sauvegarde en cours En savoir plus sur les seed phrases - Vide + Écrivez ces %d mots dans l\'ordre indiqué ci-dessous et conservez-les dans un endroit sûr et secret. Votre seed phrase - Vide + %d mots Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous @@ -527,6 +551,9 @@ Restauration du code d\'accès Cartes identiques Code d\'accès + Nos services ne sont pas disponibles dans ce pays + Modifiez-le ou confirmez-le + Votre résidence a été identifiée comme Grouper Par solde Organiser les jetons @@ -687,13 +714,14 @@ Réclamation de récompense Méthode de réception des récompenses de staking.\nElle peut être automatique, où la récompense est créditée sur votre adresse, ou manuelle, où vous devez retirer la récompense en créant une transaction pour la recevoir. Calendrier de récompenses - Il s\'agit d\'un calendrier qui détermine le moment où les participants au staking reçoivent leurs récompenses. + Il s\'agit d\'un calendrier qui détermine le moment où les participants au staking reçoivent leurs récompenses. La durée de distribution des récompenses peut varier légèrement en fonction du validateur et de la charge du réseau. Récompenses à réclamer: %s Staking %s Période de dissociation La période que vous devez attendre après avoir demandé le retrait des fonds du staking avant que les jetons ne soient disponibles. Période d\'échauffement Le temps imparti pour activer la participation au staking. + Le réseau facturera des frais d’approbation de jeton pour vérifier que vous autorisez l’utilisation de votre jeton pour le jalonnement. En utilisant la fonctionnalité de staking, vous acceptez les %1$s et %2$s du fournisseur Bloqué Migrer @@ -713,6 +741,7 @@ L\'option de staking n\'est actuellement pas disponible en raison des conditions du réseau. Veuillez réessayer plus tard. Le staking dans le réseau %1$s avec un nouveau validateur transférera automatiquement tous les fonds précédemment stakés vers ce validateur Réinvestissez vos récompenses gagnées dans le montant que vous avez staké, augmentant ainsi vos gains potentiels. + L\'option de restaker vous permet de déplacer vos fonds d\'un validateur à un autre sans avoir besoin de les déstaker. Débloquez votre argent pour le retirer du processus de staking. Le déverrouillage prend %s. Vos fonds seront disponibles à l\'utilisation après la période de déblocage de 21 jours. La récompense sera retirée en même temps que vos fonds de déblocage. Vos fonds seront disponibles pour utilisation après la période de désengagement %s. @@ -729,7 +758,10 @@ Manuel Bloquer Jour + Chaque %s Chaque jour + Chaque %s + Chaque Époque Ère Heure @@ -738,8 +770,9 @@ Récompenses Stake verrouillé Staker plus - Vous stakez %1$s et recevrez %2$s par an + Vous stakez %1$s et recevrez %2$s Appuyez pour déverrouiller + Appuyez pour déverrouiller ou voter Appuyez pour retirer Stake %s déstaker %s @@ -747,6 +780,7 @@ Dissociation Débloquer Déverrouillage + Le montant dépasse le solde misé Non-staké Unstaking En train de destaker les actifs %s @@ -768,6 +802,7 @@ Découvrez Tangem Échangez, achetez des NFT, faites des prêts et des dépôts dans plus de 100 services décentralisés différents Compatible avec Web 3.0 + Le réseau facturera des frais d\'approbation de jeton pour vérifier que vous autorisez l\'utilisation de votre jeton pour l\'échange. Échangez plus de jetons à de meilleurs taux directement dans votre portefeuille. Nouveau fournisseur d\'échange disponible ! Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %s depuis l\'échange vers l\'adresse de l\'utilisateur. @@ -818,6 +853,7 @@ Opération de : %s à : %s + validateur : %s Réessayez Vous avez scanné la même carte. Pour créer un portefeuille jumeau, vous devez scanner la carte portant le numéro %d Vous avez scanné une mauvaise carte jumelle. S\'il vous plaît, essayez-en un autre @@ -853,6 +889,7 @@ Demande de création d\'une transaction pour %1$s\n%2$s\n\nMontant : %3$s\nFrais : %4$s\nTotal : %5$s\nSolde : %6$s Impossible d\'effectuer la transaction. Fonds insuffisants. Échec de l\'établissement de la session WalletConnect. Veuillez réessayer plus tard. + Tous les jetons n\'ont pas été ajoutés à votre liste. Veuillez d\'abord les ajouter et essayez à nouveau. Jetons manquants :\n Échec de la signature du message. Veuillez réessayer Échec de l\'établissement de la session WalletConnect : erreur de délai d\'attente. Veuillez réessayer plus tard. La demande de session contient des blockchains non prises en charge pour la connexion WalletConnect. Blockchains non prises en charge :\n diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 4222204190..d5ae3921d5 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -58,7 +58,7 @@ すべてのTangemデバイスのリセットが完了していません 工場出荷時の設定にリセット セキュリティ・モード - カード設定 + デバイス設定 ネットワーク手数料に加えて、Cardanoネットワークは%2$sトークンで取引する際に%1$s ADAを請求します。 Cardanoの取引要件 %1$s 取引を行うには、いくらかのADAを入金し、ネットワーク手数料と最小ADA (5ADAを推奨) をカバーする必要があります。 @@ -82,10 +82,12 @@ %1$sへ移動 カメラへのアクセスを許可していません。プライバシー設定を調整してください。 キャンセル + 変更 アクションを選択 請求 報酬を受け取る 閉じる + 確認 続ける コピー アドレスをコピー @@ -216,7 +218,7 @@ 紹介プログラム デバイスの画面を下に向けると、残高をすばやく非表示にしたり表示したりできます。 %sハッシュ - カードID + デバイスID サポートへのお問い合わせ 他のカードをリンクする アプリ通貨 @@ -254,6 +256,7 @@ 交換中 交換中... 失敗しました + 停止中 入金済み 入金待ち 入金待ち... @@ -294,17 +297,16 @@ 取引を送信できません コインの説明エラー 取引 - ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。 選択したトークンの承認制限を指定します 金額%s 承認機能は、別のアドレスに特定の量のトークンを使用する許可を与えるために必要です。設計上スマートコントラクトは、承認しない限りトークンにアクセスできません。トークンを「ロック解除」すると、StakeKitスマート コントラクトがトークンを使用する権限が与えられます。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためにガス料金(あなたが支払う)を受け取ります。承認後、トークンをステーキングできます。 - 続行するには、StakeKitスマートコントラクトが%sを使用することを許可する必要があります + 続行するには、Polygonスマートコントラクトが%sを使用することを許可する必要があります 続行するには、%1sスマートコントラクトに%2sを使用する権限を付与してください 許可を与える 無制限 Tangemを注文 Tangemをスキャン - この情報はAIによって生成されました + この情報はAIで生成されました。 \nエラーが見つかった場合は、ここをタップしてください。 アクセスコードを変更するには、上図のようにカードまたはリングをタップし、操作が終了するまで取り外さないでください。 パスコードを変更するには、上記のようにカードをタップし、操作が終了するまで取り外さないでください。 ウォレットを作成するには、上記のようにカードをタップし、操作が終了するまで取り外さないでください。 @@ -452,7 +454,7 @@ 保護する 後で各カードおよびリングに個別のアクセスコードを設定できます。 パーソナライズする - アクセスコードはリンクされたカードおよびリング1つで復元できます。すべてのデバイスを1か所に保管しないでください。 + アクセスコードは、リンクされたカードおよびリング1つで復元できます。すべてのデバイスを1か所に保管しないでください。 復元する アクセスコードとして任意の単語、フレーズ、または数字を選択してください アクセスコードの作成 @@ -482,7 +484,7 @@ ウォレットを作成する ウォレットを作成する その他のオプション - 秘密鍵はカードまたはリング内で安全に生成されます。シードフレーズは存在しないので、誰もエクスポートしたり盗んだりすることはできません。 + 秘密鍵はチップ内で安全に生成されます。シードフレーズは存在しないので、誰もエクスポートしたり盗んだりすることはできません。 秘密鍵を非公開で生成する カードは有効化され、使用可能になりました 成功! @@ -521,7 +523,7 @@ バックアップカード バックアップカード # %d バックアップリング - バックアップカードまたはリングなし + バックアップデバイスなし 通知 バックアップデバイスが1つ追加されました カードを準備してください @@ -541,6 +543,9 @@ アクセスコードの復元 同一のカード アクセスコード + この国では当社のサービスはご利用いただけません + 変更または確認 + あなたの住居は次のように識別されています グループ 残高順 トークンを整理する @@ -695,7 +700,7 @@ 市場評価 指標 最低要件 - 報酬なし + 報酬はありません 請求中の報酬 ステーキング報酬の受け取り方法。\n自動であなたのアドレスに報酬が入金される方法と、手動で取引を生成して報酬を引き出す方法があります。 報酬スケジュール @@ -706,6 +711,7 @@ ステーキングから資金の引き出しを要求した後、トークンが利用可能になるまでの待機期間。 ウォームアップ期間 ステーキングへの参加を有効にするために割り当てられた時間。 + ネットワークは、ステーキングのためにトークンの使用を承認していることを確認するために、トークン承認手数料を請求します。 ステーキング機能を使用すると、プロバイダーの%1$sと%2$sに同意したことになります ロック中 移行 @@ -725,6 +731,7 @@ ネットワークの状態により、現在ステーキングはご利用いただけません。しばらくしてからもう一度お試しください。 新しいバリデーターで%1$sネットワークにステーキングすると、以前にステーキングされた資金はすべてこのバリデーターに自動的に転送されます。 獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。 + 再ステーキングを使うと、ステーキングを解除することなく、あるバリデータから別のバリデータに資金を移動できます。 資金をステーキングから引き出すには、ロックを解除してください。ロック解除には%sかかります。 資金は、21日間のロック解除期間後に使用可能になります。報酬は、ロック解除後の資金とともに引き出されます。 %sのステーキング解除期間後、資金はすぐ利用可能となります。 @@ -743,6 +750,8 @@ 毎日 %sごと 毎日 + %s日ごと + エポック毎 時代毎 毎時間 @@ -751,7 +760,7 @@ 報酬 ステーキングはロックされています もっとステーキングする - %1$sをステーキングし、毎%2$s報酬を受け取ります + %1$sをステーキングし、報酬%2$sを受け取ります タップしてロック解除 タップしてロック解除もしくは投票 タップして引き出す @@ -761,6 +770,7 @@ ステーキング解約中 ロック解除 ロック解除中 + 金額がステーキング残高を超えています ステーキングされていない ステーキング解除 ステーキング解除中の資産%s @@ -782,6 +792,7 @@ Tangemのご紹介 100種類以上の分散型サービスで、NFTの交換・購入や、借入・預金を行うことができます。 Web3.0対応 + ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。 より多くのトークンをより良いレートで、ウォレット内にて直接交換します。 新しいスワッププロバイダーが利用可能になりました! この金額には以下が含まれます:\n- サービスプロバイダーの手数料\n- 取引所からユーザーのアドレスに%s を送り返すためのネットワーク手数料。 @@ -868,6 +879,7 @@ %1$sの取引を作成するリクエスト\n%2$s\n\n金額: %3$s\n手数料: %4$s\n合計: %5$s\n残高: %6$s 取引を送信できません。資金が足りません。 WalletConnect セッションを確立できませんでした。しばらくしてからもう一度お試しください。 + すべてのトークンがリストには追加されませんでした。まず追加してから、もう一度お試しください。不足しているトークン: \n メッセージの署名に失敗しました。\nもう一度お試しください。 WalletConnectセッションの確立に失敗しました:タイムアウトエラー。しばらくしてもう一度お試しください。 セッションリクエストには、WalletConnect接続でサポートされていないブロックチェーンが含まれています。サポートされていないブロックチェーン: \n @@ -944,7 +956,7 @@ このカードには%sの署名のみが残っています。資金をすべて引き出す必要があります。 署名数が少ないです 異なるネットワーク上のトークンは、異なるアドレスを持つ場合があります。資金を送金する際には、アドレスがネットワークと一致していることを再確認してください。 - MATICはPOLに移行中です。ただし、期限は設定されておらず、MATICはまだ廃止されていません。MATICトークンを引き続き安全に使用することも、取引所でPOLに交換することもできます。 + MATICはPOLに移行中です。ただし、期限は設定されておらず、MATICはまだ廃止されていません。MATICトークンを引き続き安全に使用することも、POLに交換することもできます。 MATICからPOLへの移行 %d ネットワークのアドレスを取得するために、カードまたはリングを利用してください diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 170c6bfd5f..99a8ff0869 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -85,10 +85,12 @@ Перейти на %1$s Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. Отмена + Изменить Выберите действие Получить Вывести награду Закрыть + Подтвердить Продолжить Копировать Скопировать адрес @@ -125,6 +127,7 @@ Получить ваши адреса К провайдеру Перейти в токен + час Импортировать В процессе Позже @@ -181,6 +184,7 @@ Произошла ошибка. Пожалуйста, попробуйте снова. Недоступно Завершить стейкинг + неделю Да Адрес контракта скопирован! Доступные сети @@ -261,6 +265,7 @@ Обмен Обмен... Неудачно + Приостановлен Депозит получен Ожидание депозита Ожидаем пополнения... @@ -301,11 +306,10 @@ Не могу отправить транзакцию Ошибка в описании монеты Транзакция - Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. Укажите лимит доступа к выбранному токену Количество %s Функция подтверждения необходима для предоставления другому адресу разрешения на использование определенного количества ваших токенов.По замыслу смарт-контракты не могут получить доступ к вашим токенам, если вы не одобрите доступ со своей стороны. «Разблокируя» свои токены, вы даете смарт-контракту StakeKit разрешение использовать ваши активы. Майнеры сети получают компенсацию за газ (оплачиваемый вами) за запись этого действия в блокчейне. Как только разрешение будет предоставлено, вы сможете осуществить стейкинг токена. - Чтобы продолжить, вам необходимо разрешить смарт контракту StakeKit использовать ваш %s + Чтобы продолжить, вам необходимо разрешить смарт контракту Polygon использовать ваш %s Чтобы продолжить, вам нужно разрешить смарт-контракту %1s использовать ваш %2s Дать разрешение Безлимитно @@ -515,7 +519,7 @@ Ваша seed-фраза - %d слово + %d слова %d слов %d слов @@ -726,13 +730,14 @@ Способ вознаграждения Способ получения вознаграждений за стейкинг. Оно может зачисляться автоматически на ваш адрес или вручную, когда вознаграждение нужно вывести, создав транзакцию на её получение. Период вознагражения - Это период, определяющий, когда участники стейкинга получат свои вознаграждения. + Это график, который определяет, когда участники стейкинга получают свои награды. Время распределения наград может немного варьироваться в зависимости от валидатора и нагрузки сети. Вознаграждение для получения: %s Стейкинг %s Период отзыва Период, который необходимо подождать после запроса на вывод средств из стейкинга, прежде чем токены станут доступны. Период прогрева Время, необходимое для начала процесса стейкинга и активации процесса начисления наград + Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для стейкинга. Пользуясь стейкинг сервисом, вы соглашаетесь с %1$s и %2$s Заблокировано Переместить @@ -752,6 +757,7 @@ Стейкинг временно недоступен из-за проблем в сети. Пожалуйста, попробуйте позже. Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него. Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход + Рестейк позволяет вам переместить средства из одного валидатора в другого без необходимости выхода из стейкинга. Разблокируйте свои средства, чтобы вывести их из стейкинга. Разблокировка займёт %s. Ваши средства будут доступны для использования после 21-дневного периода отзыва. Награда будет выведена вместе с вашими выводими средствами. Ваши средства будут доступны после %s периода отзыва. @@ -767,7 +773,10 @@ Вручную За блок Ежедневно + Каждый %s Каждый день + Каждые %s дня + Каждые За эпоху За эру Ежечасно @@ -776,7 +785,7 @@ Вознаграждения Стейкинг закрыт Застейкать еще - Вы стейкаете %1$s и будете получать %2$s ежегодно + Вы стейкаете %1$s и будете получать награду %2$s Нажмите для разблокировки Нажмите для разблокировки Нажмите для вывода @@ -786,6 +795,7 @@ Отзыв Разблокировать Разблокировка + Сумма превышает застейканный баланс Вывод из стейкинга Завершение стейкинга Завершение стейкинга %s @@ -807,6 +817,7 @@ Встречайте Tangem Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах Поддержка Web 3.0 + Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена. Обменивайте больше токенов по лучшим курсам прямо в вашем кошельке. Новый провайдер обмена! В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя. diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 1d8ae684fb..d9837db78c 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -12,19 +12,19 @@ Не вдається відправити транзакцію Обраний гаманець не підтримує мережу %1$s Щоб активувати криптографічне шифрування блокчейну %1$s, вам потрібно скинути налаштування гаманця до заводських. Зніміть свої кошти перед цим, щоб переконатися, що ви їх не втратите, а потім завершіть процес скидання. Вхід до поточного гаманця буде неможливий після скидання. - Токени в мережі %1$s не підтримуються цією карткою через обмеження прошивки. - У вас виникли труднощі зі скануванням картки? + Токени в мережі %1$s не підтримуються цією карткою або кільцем через обмеження прошивки. + У вас виникли труднощі зі скануванням картки або кільця? Ця картка не призначена для роботи з цим додатком Комісія за замовчуванням Увімкніть комісію за замовчуванням, щоб автоматично встановлювати комісію за транзакцію і пропускати сторінку комісій під час відправлення коштів. Ви завжди можете повернутися до цієї сторінки за необхідності. Перейдіть до налаштувань, щоб увімкнути біометричну автентифікацію в додатку Tangem Увімкнути біометричну автентифікацію Усі збережені коди доступу будуть видалені. Вам доведеться вводити код доступу при роботі з гаманцем. - При відключенні функції збереження гаманця всі збережені до цього гаманці будуть видалені зі застосунку. + При відключенні функції збереження гаманця всі до цього збережені гаманці будуть видалені зі застосунку. Зберегти код доступу - Підключіть функцію збереження кодів доступу від карток на телефоні у зашифрованому виді, і при роботі з карткою замість коду доступу буде запитуватися біометрична автентифікація. + Підключіть функцію збереження кодів доступу від карток на телефоні у зашифрованому виді, і при роботі з карткою або кільцем замість коду доступу буде запитуватися біометрична автентифікація. Зберігати гаманець у додатку - Увімкніть функцію привʼязки карток у застосунку, а також можливість біометричної автентифікації. Підпис транзакції й надалі буде потребувати картку. + Увімкніть функцію привʼязки карток у застосунку, а також можливість біометричної автентифікації. Підпис транзакції й надалі буде потребувати картку або кільце. Темна Світла Системна @@ -34,34 +34,34 @@ Більше не показувати Зрозуміло Баланси приховані - Будь ласка, відскануйте картку - Будь ласка, повторіть спробу через 30 секунд або відскануйте картку + Будь ласка, відскануйте картку або кільце + Будь ласка, спробуйте знову через 30 секунд або відскануйте картку або кільце Забагато спроб Ви вимкнули біометричну автентифікацію на своєму телефоні і не зможете зберігати гаманці в додатку. Щоб зберегти гаманці, будь ласка, увімкніть функцію біометричної автентифікації в налаштуваннях телефону. Почніть процес резервного копіювання Використовуйте банківську картку або інші способи оплати - %d картка - %d картки - %d карток - %d карток + %d пристрій + %d пристрої + %d пристроїв + %d пристроїв - Вимкніть цю опцію, якщо ви не хочете, щоб ця картка використовувалася для скидання кодів доступу до інших карток у цьому гаманці. Зверніть увагу, що це також унеможливить скидання коду доступу на цій картці. + Вимкніть цю опцію, якщо не хочете, щоб ця картка використовувалася для скидання кодів доступу на інших картках у цьому гаманці. Зверніть увагу, скидання коду доступу також не буде доступним на цій картці. Дозволяє використовувати цю картку для скидання коду доступу на інших картках у цьому гаманці Відновлення коду доступу Скинути Ви впевнені, що хочете це зробити? Зміна коду доступу - Код доступу буде змінено лише на цій картці - Усі картки у вибраному гаманці було скинуто до заводських налаштувань. Тепер ви можете створити новий гаманець. + Код доступу буде змінений лише на цій картці або кільцю + Усі пристрої Tangem у вибраному гаманці було скинуто до заводських налаштувань. Тепер ви можете створити новий гаманець. Скидання завершено - Ви хочете скинути наступну картку в цьому гаманці? - Скидання картки - Рекомендуємо завершити процес скидання для всіх карток у цьому гаманці - Ви скинули не всі картки + Бажаєте скинути наступний пристрій від цього гаманця? + Скидання гаманця + Рекомендуємо завершити процес скидання для всіх пристроїв Tangem гаманця + Ви скинули не всі Tangem пристрої Скинути до заводських налаштувань Тип безпеки - Налаштування картки + Налаштування пристрою Крім мережевих комісій, мережа Cardano стягує %1$s ADA при транзакції з токеном %2$s Вимоги до транзакції Cardano Щоб здійснити транзакцію %1$s, внесіть певну суму ADA для покриття мережевої комісії та мінімальне значення ADA (рекомендовано 5 ADA) @@ -85,10 +85,12 @@ Перейдіть до %1$s Ви не надали доступ до камери, будь ласка, змініть налаштування конфіденційності Скасувати + Змінити Виберіть дію Отримати Отримати винагороди Закрити + Підтвердити Продовжити Копіювати Скопіювати адресу @@ -122,7 +124,7 @@ За ринком Повільно Швидкість та комісія - Отримати адреси + Отримати ваші адреси Перейти до провайдера Перейти до токену година @@ -141,6 +143,7 @@ Зараз ОК Основна картка + Основне кільце Парольна фраза Вставити Політикою конфіденційності @@ -218,13 +221,13 @@ Код доступу Перед скануванням картки вам потрібно буде ввести правильний код доступу Затримка сканування - Цей механізм захищає картку від безконтактних атак. Між скануванням картки та виконанням команди буде додана затримка. + Цей механізм захищає картку або кільце від безконтактних атак. Між скануванням картки та виконанням команди буде додана затримка. Пароль Перед виконанням будь-якої команди, що тягне за собою зміну стану картки, вам необхідно буде ввести пароль. Реферальна програма Переверніть екран пристрою вниз, щоб швидко приховати та відобразити баланси %s хешів - Номер картки + Номер пристрою Звернутися у підтримку Додати більше карток Валюта застосунку @@ -235,7 +238,7 @@ Деталі Перевірте підключення до інтернету або змініть мережу Умови використання - Ви використали картку від іншого гаманця. Прикладіть картку, пов\'язану з цим гаманцем. + Ви використали картку або кільце від іншого гаманця. Прикладіть картку або кільце, пов\'язану з цим гаманцем. Мої токени У вас немає доданих токенів. Додайте токени для обміну Недоступний для обміну з %s @@ -262,6 +265,7 @@ Обмін Обмін... Не вдалося + Призупинено Депозит отримано Очікуємо на депозит Очікуємо на депозит... @@ -289,38 +293,38 @@ Токенів не знайдено. Будь ласка, спробуйте інший запит ID: %s ID транзакції скопійовано - Конвертуйте одну з ваших валют в іншу + Обміняйте будь-який актив у своєму портфелі на цей токен Інформація нижче не є обов\'язковою. Ви можете стерти її, якщо бажаєте. Розкажіть, яких функцій вам не вистачає, і ми спробуємо вам допомогти. - Розкажіть, будь ласка, яку картку ви маєте? + Розкажіть, будь ласка, яку картку або кільце ви маєте? Привіт, команда підтримки, Будь ласка, розкажіть нам більше про вашу проблему. Кожна дрібниця може допомогти. Мої пропозиції - Не вдається відсканувати картку + Не вдається відсканувати картку/кільце Звернення в підтримку Звернення в підтримку Tangem Не вдається відправити транзакцію Помилка в описі монети Транзакція - Мережа стягує комісію за схвалення токену за підтвердження, що саме ви дозволяєте використовувати ваш токен для обміну. Вкажіть ліміт доступу для обраного токена Кількість %s Функція підтвердження необхідна для надання дозволу іншій адресі на використання певної кількості ваших токенів. За задумом, смарт-контракти не можуть отримати доступ до ваших токенів без вашого схвалення. \"Розблоковуючи\" свої токени, ви дозволяєте смарт-контракту StakeKit використовувати їх. Майнери мережі отримують плату за газ (сплачену вами), щоб зафіксувати цю дію в блокчейні. Ви зможете застейкати свій токен після того, як дасте дозвіл. - Щоб продовжити, вам потрібно дозволити смарт-контракту StakeKit використовувати ваш %s - Щоб продовжити, вам потрібно надати дозвіл смарт-контракту %1s використовувати ваш %2s + Щоб продовжити, вам потрібно дозволити смарт-контракту Polygon використовувати ваш %s + Щоб продовжити, вам потрібно дозволити смарт-контракту %1s використовувати ваш %2s Надати дозвіл Необмежено Купити Сканувати - Ця інформація була створена за допомогою ШІ - Щоб змінити код доступу, прикладіть картку, як показано вище, і не прибирайте її до закінчення операції + Ця інформація була створена за допомогою ШІ.\nНатисніть тут, якщо знайшли помилку. + Щоб змінити код доступу, прикладіть картку або кільце, як показано вище, і не прибирайте її до закінчення операції Щоб змінити пароль, прикладіть картку, як показано вище, і не прибирайте її до закінчення операції Щоб створити гаманець, прикладіть картку, як показано вище, і не прибирайте її до завершення операції + Щоб створити гаманець, торкніться кільця, як показано вище, і не прибирайте до кінця операції Прикладіть картку #%s для скидання Прикладіть, щоб відсканувати Прикладіть, щоб підписати - Прикладіть картку - Ви оновили біометричні дані, відскануйте свою картку для входу + Прикладіть картку або кільце + Ви оновили дані біометрії, відскануйте свою картку або кільце для входу Ваш баланс повинен перевищувати суму комісії для здійснення переказу Недостатньо балансу У вас недостатньо Mana для цієї транзакції. Будь ласка, зачекайте, поки Mana буде поповнена. Ваш баланс Mana становить %1$s/%2$s @@ -332,7 +336,7 @@ Щоб почати відстежувати свої криптоактиви та транзакції, додайте токени Керування токенами Для доступу до всіх мереж необхідно відсканувати картку - Відскануйте картку + Відскануйте картку або кільце Насолоджуйтесь %1$s комісією за обслуговування на свопах через Changelly з %2$s по %3$s лютого. Обмін із Changelly, %s комісії Токени @@ -341,7 +345,7 @@ Ринкова капіталізація Спочатку був створений блокчейн криптовалюти Основна мережа - Використання неосновних мереж для токенів дозволяє забезпечити крос-блокчейн інтероперабельність, що дає змогу використовувати активи в різноманітних децентралізованих додатках та смарт-контрактах на різних платформах. Однак це часто вимагає наявності кастодіана або смарт-контракту для безпечного зберігання оригінального активу, що вводить централізацію та ризик контрагента. + Використання нерідних мереж для токенів забезпечує сумісність між блокчейнами, дозволяючи використовувати активи в різноманітних децентралізованих програмах і смарт-контрактах на різних платформах. Однак для цього часто використовується зберігач або смарт-контракт для надійного зберігання оригінального активу, вводячи централізацію та ризик контрагента. Не оригінальний або первинний блокчейн, на якому розміщено токен Неосновні мережі Оберіть мережі @@ -368,7 +372,7 @@ Доступні мережі Моє портфоліо Маркет - Щоб згенерувати адреси для обраних мереж, потрібно відсканувати свою картку Tangem + Щоб згенерувати адреси для обраних мереж, відскануйте вашу картку або кільце Tangem гаманця Щоб додати токени, потягніть вгору або торкніться панелі пошуку Дані цього розділу отримані з наступних мереж: %s Не вдалося завантажити дані... @@ -396,10 +400,10 @@ В тренді Про %s - біржа - біржі - бірж - бірж + %d біржа + %d біржі + %d бірж + %d бірж На основі %d оцінки @@ -464,11 +468,11 @@ Потягніть вгору або торкніться панелі пошуку, щоб додати токени безпосередньо з маркету Додати токени Функція NFC недоступна на вашому пристрої - Вам потрібно встановити єдиний код доступу для захисту всіх ваших карток + Налаштуйте єдиний код доступу для захисту всіх ваших карток або кілець Захист - Пізніше ви зможете налаштувати індивідуальний код доступу до кожної картки + Встановіть індивідуальний код доступу для кожної картки або кільця пізніше. Персоналізація - Код доступу можна відновити за допомогою прив\'язаної картки. Не зберігайте всі картки в одному місці. + Відновіть код доступу за допомогою прив\'язаної картки або кільця. Не зберігайте всі свої пристрої в одному місці. Відновлення Оберіть будь-яке слово, фразу або число в якості коду доступу Створити код доступу @@ -476,27 +480,29 @@ Повторно введіть код доступу Код доступу повинен містити не менше 4 символів Введені коди доступу не збігаються - Необхідно повторити операцію. Карту буде скинуто до заводських налаштувань. + Необхідно повторити операцію, при цьому картка буде скинута до заводських налаштувань. Помилка активації Додати токени - Ви додали одну резервну картку. Після завершення процесу резервного копіювання ви не зможете додати більше резервних карток. Якщо у вас є ще одна картка, додайте її до резервної копії. Ви бажаєте продовжити? + Ви додали одну резервну картку або кільце. Після завершення процесу резервного копіювання ви не зможете додати їх більше. Якщо у вас є ще одна картка або кільце, додайте її до резервної копії. Бажаєте продовжити? Процес резервного копіювання частково завершено. Ви не можете вийти з нього зараз. Парольна фраза – це розширена функція безпеки, яку використовують криптогаманці. Вона додає додаткове слово або фразу на ваш вибір до вже існуючої seed - фрази, щоб розблокувати абсолютно новий набір адрес. - Додати резервну картку - Відсканувати картку #%d + Додати картку або кільце + Сканувати картку + Сканувати картку #%d Створити резервну копію - Відсканувати основну картку + Сканувати картку + Сканувати кільце Перейти до мого гаманця Завершення бекапу Отримати криптовалюту Сканувати основну картку Пропустити Як це працює? - Давайте згенеруємо всі ключі на вашій картці та створимо безпечний гаманець + Давайте згенеруємо всі ключі на вашій картці або кільці та створимо безпечний гаманець Створити гаманець Створити гаманець Інші опції - Ваші ключі будуть надійно згенеровані всередині картки. Ніякої seed-фрази, а це означає, що ніхто не зможе її експортувати або вкрасти. + Ваші ключі будуть надійно згенеровані всередині чіпу. Ніякої seed-фрази, а це означає, що ніхто не зможе її експортувати або вкрасти. Генеруйте ключі приватно Ваша картка активована та готова до використання Успішно! @@ -514,7 +520,7 @@ Ваша seed-фраза - %d слово + %d слова %d слів %d слів @@ -522,27 +528,30 @@ Щоб імпортувати гаманець, введіть seed-фразу в поле нижче Згенерувати seed-фразу Імпорт гаманця - Seed-фраза — це набір слів, який дозволяє відновити ваш гаманець. На відміну від ключів, що генеруються карткою, seed-фраза не захищена і може бути скопійована та викрадена. Використовуйте цю опцію на свій власний ризик. + Seed-фраза — це набір слів, який надає можливість відновити гаманець. На відміну від ключів, що генеруються карткою або кільцем, seed-фраза не захищена і може бути скопійована та вкрадена. Використовуйте цей варіант на свій власний страх та ризик. Використовувати seed-фразу Невірна seed-фраза. Будь ласка, перевірте порядок слів. Невірна seed-фраза. Будь ласка, перевірте правопис. Застаріло Щоб перевірити чи правильно ви записали seed-фразу, введіть 2-е, 7-е та 11-те слова Отже, давайте перевіримо - Щоб почати процес резервного копіювання, додайте одну або дві резервні картки. - Ви можете додати ще одну картку або завершити процес резервного копіювання + Щоб почати процес резервного копіювання, додайте одну або дві резервні картки або кільця. + Ви можете додати ще одну картку або кільце або завершити процес резервного копіювання Підготуйте резервну картку з номером %s - Відскануйте основну картку, щоб почати процес резервного копіювання. + Відскануйте основну картку або кільце, щоб почати процес резервного копіювання. Підготуйте основну картку з номером %s - Ваша картка налаштована та готова до використання. - Додано максимальну кількість карток. Завершіть процес резервного копіювання. - Активація картки + Підготуйте кільце та натисніть кнопку сканування нижче + Ваш гаманець налаштований та готовий до використання. + Додано максимальну кількість пристроїв. Завершіть процес резервного копіювання. + Активація гаманця + Резервна картка Резервна картка #%d - Немає резервних карток + Резервне кільце + Немає резервних пристроїв Сповіщення - Додано одну резервну картку + Додано один резервний пристрій Підготуйте свою картку - Додано дві резервні картки + Додано два резервних пристрої Поповніть гаманець на будь-яку суму, щоб почати користуватися карткою Щоб почати, просто поповніть гаманець більше, ніж на %1$s %2$s Купити криптовалюту @@ -550,7 +559,7 @@ Активація гаманця Процес поєднання карток частково завершено. Ви не можете вийти з нього зараз. Якщо процес створення гаманця буде перервано будь-яким чином, вам доведеться почати все спочатку - Ви можете створити резервну копію ключів на одній або двох інших порожніх картках Tangem Wallet. + Ви можете створити резервну копію своїх ключів на одній або двох інших порожніх картках або кільцях Tangem Wallet. Код доступу можна відновити за допомогою однієї з резервних карток. Всі резервні карти можна використовувати як повнофункціональні з ідентичними ключами. Ви зможете встановити код доступу для захисту своїх гаманців. @@ -558,6 +567,9 @@ Відновлення коду доступу Ідентичні картки Код доступу + Наші сервіси недоступні в цій країні + Змінити або підтвердити + Ваше місце проживання визначено як Групами За балансом Сортування токенів @@ -568,7 +580,7 @@ Доступ до камери заборонено %1$s (%2$s) у мережі %3$s Надсилайте лише %s на цю адресу. Надсилання будь-якої іншої валюти призведе до її незворотної втрати. - Перекажіть кошти з іншого гаманця або біржі + Переказуйте кошти з будь-якого гаманця або біржі Взяти участь Не вдалося завантажити інформацію по реферальній програмі. Будь ласка, спробуйте пізніше. Не вдалося завантажити інформацію по реферальній програмі. Код помилки: %s. Будь ласка, спробуйте пізніше. @@ -605,13 +617,13 @@ Скинути картку Я розумію, що після виконання цієї дії у мене більше не буде доступу до поточного гаманця Я розумію, що не можу використати цю картку для відновлення свого коду доступу на інших картках поточного гаманця - Скидання до заводських налаштувань призведе до повного видалення гаманця з обраної картки. Ви не зможете відновити поточний гаманець або використати картку для відновлення коду доступу. - Скидання до заводських налаштувань призведе до повного видалення гаманця з обраної картки. Ви не зможете відновити поточний гаманець. + Скидання до заводських налаштувань призведе до повного видалення гаманця з обраної картки або кільця. Ви не зможете відновити поточний гаманець або використати цю картку або кільце для відновлення коду доступу. + Скидання до заводських налаштувань призведе до повного видалення гаманця з обраної картки або кільця. Ви не зможете відновити поточний гаманець. Власникам кілець - 3 обміни без комісії на Changelly до 15.11! Обмін з 0%% комісією! У вас є банківська картка іншої країни та посвідка на проживання або реєстрація за межами Російської Федерації? Російські банківські картки наразі не приймаються - Увійдіть у додаток та слідкуйте за своїм балансом без сканування картки + Увійдіть у додаток та слідкуйте за своїм балансом без сканування картки чи кільця Доступ до додатку Використовувати біометрію Для взаємодії з гаманцем будуть запитуватися біометричні дані замість коду доступу @@ -619,12 +631,12 @@ Схоже, що у вас відключена біометрична автентифікація, вона необхідна для збереження гаманців Увімкніть біометричну автентифікацію Бажаєте використовувати біометрію? - Зверніть увагу, що для здійснення транзакції з вашими коштами вам все одно знадобиться ваша картка + Зверніть увагу, що для здійснення транзакції з вашими коштами вам все одно знадобиться ваша картка або кільце Сканувати - Відскануйте картку, щоб змінити її налаштування. Зміни торкнуться лише відсканованої картки і не вплинуть на інші картки, прив\'язані до вашого гаманця. - Підготуйте свою картку + Відскануйте картку або кільце, щоб змінити її налаштування. Зміни торкнуться лише тої картки або кільця, які ви відсканували, і не вплинуть на інші пристрої, прив\'язані до вашого гаманця. + Підготуйте свій Tangem! Вже включено до введеної адреси - Сума комісії у %s разів перевищує рекомендовану. Переконайтеся, що користувацькі налаштування вірні. + Сума комісії у %s разів перевищує рекомендовану. Переконайтеся, що вказана комісія вірна. Ви вказали комісію нижче рекомендованої, це може спричинити затримку вашої транзакції. Продовжити? Причина: %1$s\nКод: %2$s Транзакція не завершена @@ -659,7 +671,7 @@ Сума перевищує баланс Для збереження вашого акаунту у блокчейні та захисту від можливих ризиків необхідний баланс не менше %s. Ця сума залишиться на вашому рахунку та не може бути знята. Екзистенційний депозит - Сума комісії в %s разів перевищує рекомендовану. Переконайтеся, що користувацькі налаштування вірні. + Сума комісії в %s разів перевищує рекомендовану. Переконайтеся, що вказана комісія вірна. Встановлена комісія завелика Через особливості мережі %1$s комісія за переказ всього балансу вища. Щоб зменшити комісію, Ви можете залишити %2$s. Підвищена комісія @@ -693,12 +705,12 @@ Ви надсилаєте **%1$s** і %2$s Ви надсилаєте **%1$s** комісія мережі буде покрита, використовуючи %1$s енергію - комісія мережі буде зменшена, витрачаючи %1$s + Комісія мережі буде зменшена, за рахунок використання %1$s енергії включно з комісією мережі %1$s Транзакція успішно підписана і відправлена до блокчейну. Баланс гаманця буде оновлено через деякий час %1$s — це монета у мережі Tron. Щоб розрахувати комісію та здійснити транзакцію, вам необхідно внести певну кількість Tron (TRX) на свій рахунок. Трансакцію надіслано - Підготуйте до сканування картку, яку потрібно налаштувати. + Підготуйтеся до сканування кільця або картки, яку ви хочете налаштувати. Забути гаманець Це призведе до видалення гаманця з застосунку. Сам гаманець можна додати знову. Ім\'я @@ -718,17 +730,18 @@ Рейтинг ринку Метрики Мінімальні вимоги - Немає винагород, щоб отримати + Немає винагород, до отримання Отримати винагороду - Спосіб отримання винагороди за стейкінг. Його можна отримати автоматично або вручну. + Метод отримання винагород за стейкінг.\nВін може зараховуватися автоматично на вашу адресу або вручну, коли винагороду треба вивести, створивши транзакцію на її отримання. Розклад винагород - Це графік, який визначає коли учасники стейкінгу отримують свої винагороди. + Це графік, який визначає, коли учасники стейкінгу отримують свої винагороди. Час розподілу винагород може дещо відрізнятися залежно від валідатора та навантаження на мережу. Нагороди для отримання: %s Стейкінг %s Період розблокування Період, який ви повинні чекати після запиту на виведення коштів зі стейкінгу, перш ніж токени стануть доступними. Період блокування Відведений час для активації участі в стейкінгу. + Комісія мережі за схвалення токену, щоб верифікувати, що саме ви даєте дозвіл на використання вашого токена для стейкінгу. Використовуючи функцію стейкінгу, ви погоджуєтесь з %1$s та %2$s Заблоковано Перемістити @@ -739,19 +752,20 @@ Стейкайте безпечно та почніть отримувати винагороди щоденно Стейкайте безпечно та почніть отримувати винагороди щогодини Стейкайте безпечно та почніть отримувати винагороди щомісяця - Стейкінг дозволяє заробляти %1$s. Ваші винагороди за стейкінг зараховуються щодня. - Стейкінг дозволяє заробляти %1$s. Ваші винагороди за стейкінг зараховуються кожну годину. - Стейкінг дозволяє заробляти %1$s. Ваші винагороди за стейкінг зараховуються щомісяця. - Стейкінг дозволяє заробляти %1$s. Ваші винагороди за стейкінг зараховуються щотиждня. + Стейкінг дає змогу вам отримувати %1$s. Винагорода буде зараховуватися кожного дня. + Стейкінг дає змогу вам отримувати %1$s. Винагорода буде зараховуватися кожну годину. + Стейкінг дає змогу вам отримувати %1$s. Винагорода буде зараховуватися щомісяця. + Стейкінг дає змогу вам отримувати %1$s. Винагорода буде зараховуватися кожен тиждень. Стейкайте безпечно та почніть отримувати винагороди щотижня Отримуйте винагороду за стейкінг Стейкінг тимчасово недоступний через проблеми в мережі. Будь ласка, спробуйте пізніше. Стейкінг в мережі %1$s з новим валідатором автоматично переведе всі раніше застейкані кошти до цього валідатора Реінвестуйте зароблені винагороди у суму стейкінгу, щоб збільшити потенційний прибуток. + Рестейк дозволяє вам перемістити ваші кошти від одного валідатора до іншого без необхідності виводити кошти зі стейкінгу Розблокуйте свої кошти, щоб вивести їх зі стейкінгу. Розблокування займе %s. Ваші кошти будуть доступні для використання після закінчення 21-денного періоду розблокування. Винагорода буде отримана разом з вашими незастейканими коштами. Ваші кошти будуть доступні після %s періоду розблокування. - Тепер ви можете вивести свої кошти, вони будуть доступні для використання відразу + Ви можете вивести свої кошти зі стейкінгу, вони будуть доступні для використання відразу Стейкінг в мережі Tron з новим валідатором автоматично переведе всі раніше застейкані кошти до цього валідатора Підготовка Готова до виведення @@ -762,19 +776,21 @@ Переголосувати Автоматично Вручну - Блок - День + За блок + Щоденно Кожен %s Щодня - Епоха - Ера - Година + Кожні %s дні + Кожні + За епоху + За еру + Погодинно Місяць - Тиждень + Потижнево Винагороди Стейкінг закрито Застейкати більше - Ви стейкаєте %1$s і будете отримувати %2$s щороку + Ви стейкаєте %1$s і будете отримуватиме винагороду %2$s Натисніть, щоб розблокувати Натисніть, щоб розблокувати або проголосувати Натисніть, щоб вивести кошти @@ -784,6 +800,7 @@ Розблокування Розблокувати Розблокування + Сума перевищує баланс стейкінгу Вивід зі стейкінгу Зняти зі стейкінгу Виведення зі стейкінгу %s @@ -794,17 +811,18 @@ Проголосувати Зняти Ваші стейкінги - Тримайте свою криптовалюту в безпеці. Приватні ключі надійно зберігаються на картці. + Тримайте свою криптовалюту в безпеці. Приватні ключі надійно зберігаються на картці або кільці Революційний апаратний гаманець - До трьох карток з одним гаманцем + До трьох карток або кілець з одним гаманцем Всі ключі у безпеці - Апаратний гаманець для ваших біткоїнів, ефіріуму та багатьох інших валют одночасно — і все це на одній картці + Апаратний гаманець для ваших біткоїнів, ефіріуму та багатьох інших валют одночасно — і все це на одній картці або кільці Тисячі криптовалют - Використовуйте його в дорозі, будь-де і будь-коли. Ніяких дротів чи батарейок. Просто прикладіть картку до телефону, коли вам потрібна криптовалюта. + Використовуйте його в дорозі, будь-де і будь-коли. Ніяких дротів чи батарейок. Просто прикладіть картку або кільце до телефону, коли вам потрібна криптовалюта. Гаманець для кожного Зустрічайте Tangem Обмінюйте, купуйте NFT, отримуйте позики та робіть депозити у понад 100 різних децентралізованих сервісах Web 3.0 сумісність + Мережа стягує комісію за схвалення токену за підтвердження, що саме ви дозволяєте використовувати ваш токен для обміну. Обмінюйте більше токенів за вигіднішим курсом прямо у своєму гаманці. З\'явився новий провайдер обмінів! Сума включає: \n• комісію постачальника послуг\n• комісію мережі за відправлення %s з біржі назад на адресу користувача. @@ -861,28 +879,28 @@ Ви відсканували не ту twin-картку. Будь ласка, спробуйте відсканувати іншу Це картка, яку ви тримаєте в руках. У парної картки номер %s.\n\nОбидві картки можна використовувати для зняття коштів з цього гаманця. Один гаманець. Дві картки. - Відсканувати картку #%s + Сканувати картку #%s Створення гаманця Відскануйте twin-картку # %s Підготовка картки Tangem Twin Ця дія є незворотною. Ви не матимете доступу до старого гаманця. Прикладіть twin-картку з номером %s та не прибирайте її до завершення операції - Використовуйте %s або відскануйте картку, щоб отримати доступ до свого гаманця + Використовуйте %s або відскануйте картку/кільце, щоб отримати доступ до свого гаманця Будьте в курсі останніх функцій та новин Дізнавайтеся першими про нові акції Бажаєте використовувати Push-повідомлення? Додати новий гаманець Ви впевнені, що хочете видалити цей гаманець? - Сталася помилка, будь ласка, відскануйте свою картку, щоб увійти в систему + Сталася помилка, будь ласка, відскануйте свою картку або кільце, для входу Цей гаманець вже збережено, ви можете додати інший Гаманець з назвою %s вже існує Назва гаманця - Перейменувати гаманець + Перейменування гаманця Розблокувати все Розблокувати все з %s Блокчейн недоступний. Спробуйте пізніше - Відскануйте картку + Відскануйте картку або кільце Запит на підпис повідомлення.\n\n%s Dapp %1$s, запит на\nпідпис транзакції BNB.\n\n%2$s Торговий ордер на %1$s\nЦіна: %2$s\nСума до отримання: %3$s\nСума до сплати: %4$s @@ -891,12 +909,13 @@ Запит на створення транзакції для %1$s\n%2$s\n\nСума: %3$s\nКомісія: %4$s\nВсього: %5$s\nБаланс: %6$s Неможливо відправити транзакцію. Недостатньо коштів. Не вдалося встановити сеанс WalletConnect. Будь ласка, спробуйте пізніше. + Не всі токени додано до вашого списку. Будь ласка, додайте їх спочатку і спробуйте ще раз. Відсутні токени:\n Не вдалося підписати повідомлення.\nБудь ласка, спробуйте ще раз Не вдалося створити сеанс WalletConnect за відведений час. Будь ласка, спробуйте пізніше. Запит на підключення сеансу WalletConnect містить непідтримувані блокчейни. Непідтримувані блокчейни:\n Зв\'язок з цим Dapp сервісом не може бути встановлений через його технічну реалізацію. Ми зіткнулися з невідомою помилкою. Повідомлення про помилку: %s. Якщо проблема не зникає — зверніться до нашої служби підтримки. - Невірна картка обрана в додатку Tangem + Невірна картка або кільце обрана в додатку Tangem Не вдалося створити транзакцію з даних Dapp. Код: %s Ми зіткнулися з невідомою помилкою. Код помилки: %d. Якщо проблема не зникає — зверніться до нашої служби підтримки. Немає відкритих сеансів WalletConnect @@ -921,10 +940,10 @@ Немає підключення до інтернету Налаштування гаманця Tangem - Використовуйте %s або відскануйте картку, щоб розблокувати доступ до гаманця + Використовуйте %s або відскануйте картку/кільце, щоб розблокувати доступ до вашого гаманця Процес підтвердження наразі триває і буде завершено найближчим часом Підтвердження в процесі - Схоже, що активація картки була виконана неправильно. Це може бути пов\'язано з проблемою з модулем NFC вашого пристрою або неправильним прикладанням картки до пристрою. Зверніться за допомогою до нашої служби підтримки. + Схоже, що активація карток або кільця була виконана неправильно. Це може бути пов\'язано з проблемою з модулем NFC вашого пристрою або неправильним прикладанням картки або кільця до телефону. Будь ласка, зверніться за допомогою до нашої служби підтримки для уточнення деталей. Помилка активації За рішенням розробників мережі BNB стандарт BEP-2 перестане підтримуватись у червні 2024 року. Щоб не втратити свої активи, їх необхідно конвертувати у стандарт BEP-20. Використовуйте функцію обміну, щоб перевести їх у мережу BNB Smart Chain. Відключення мережі BNB Beacon Chain @@ -943,7 +962,7 @@ Для роботи з мережею вимагається депозит Обмін буде доступний після завершення %s транзакції У вас є активна транзакція - Затвердження обміну триває і незабаром буде завершено + Дозвіл обміну триває і незабаром буде завершено Затвердження в процесі Мінімальна сума обміну становить - %1$s. Будь ласка, переконайтеся, що залишок на рахунку після обміну буде не менше за %2$s. У вашому списку немає доступних монет для обміну %s @@ -967,13 +986,13 @@ На цій картці залишається лише %s підписів. Ви повинні вивести всі свої кошти. Низька кількість підписів Токени в різних мережах можуть мати різні адреси. Переконайтеся, що ваша адреса відповідає мережі, коли переказуєте кошти. - Токен MATIC мігрує до POL. Однак кінцевого терміну не встановлено і MATIC ще не застарілий. Ви можете безпечно продовжувати використовувати токен MATIC або обміняти його на POL. + Токен MATIC мігрує до POL. Однак кінцевого терміну не встановлено і MATIC ще не застарілий. Ви можете спокійно продовжувати використовувати токен MATIC або обміняти його на POL. Міграція MATIC у POL - Використовуйте вашу картку, щоб отримати адресу для %d мережі - Використовуйте вашу картку, щоб отримати адреси для %d мереж - Використовуйте вашу картку, щоб отримати адреси для %d мереж - Використовуйте вашу картку, щоб отримати адреси для %d мереж + Використовуйте вашу картку або кільце, щоб отримати адресу для %d мережі + Використовуйте вашу картку або кільце, щоб отримати адреси для %d мереж + Використовуйте вашу картку або кільце, щоб отримати адреси для %d мереж + Використовуйте вашу картку або кільце, щоб отримати адреси для %d мереж Деякі адреси відсутні Мережа наразі недоступна. Будь ласка, спробуйте пізніше. @@ -1000,11 +1019,11 @@ Ви не завершили резервне копіювання. Бажаєте продовжити? Так, поновити Відмовитися - Якщо зараз відмовитися, то доведеться скинути картки до заводських налаштувань, щоб почати спочатку + Якщо зараз відмовитися, то доведеться скидувати пристроїв до заводських налаштувань, щоб почати спочатку Відновити бекап Це незворотна дія Увійдіть за допомогою %s - Сканувати картку - Використовуйте %s або відскануйте картку для доступу в додаток + Сканувати картку або кільце + Використовуйте %s або відскануйте картку або кільце для входу у додаток З поверненням! diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index a0beef5668..9b1c743f98 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -83,10 +83,12 @@ Go to %1$s You have not given access to your camera, please adjust your privacy settings Cancel + Change Choose action Claim Claim rewards Close + Confirm Continue Copy Copy address @@ -257,6 +259,7 @@ Exchanging Exchanging... Failed + Paused Deposit received Awaiting deposit Awaiting deposit... @@ -297,17 +300,16 @@ Can\'t send a transaction Coin description error Transaction - The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. Specify the approve limit for the selected token Amount %s The Approve function is needed to grant permission to another address to use a specific amount of your tokens. By design, smart contracts can\'t access your tokens unless you approve. By \"unlocking\" your tokens, you authorize the StakeKit smart contract to use them. The network\'s miners receive a gas fee (paid by you) to record this action on the blockchain. You can stake your token after giving approval. - To continue you need to allow StakeKit smart contract to use your %s + To continue you need to allow Polygon smart contract to use your %s To continue, grant %1s smart contracts permission to use your %2s Give Permission Unlimited Order Tangem Scan Tangem - This information was generated with AI.\nTap here, If you find any errors. + This information was generated with AI.\nTap here, if you find any errors. To change the access code tap the card or ring as shown above and do not remove until the end of the operation To change the passcode tap the card as shown above and do not remove until the end of the operation To create the wallet tap the card as shown above and do not remove until the end of the operation @@ -337,7 +339,7 @@ Coin market cap Blockchain the cryptocurrency was initially created Native network - Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. + Using non-native networks for tokens enables cross-blockchain interoperability, allowing assets to be utilized in diverse decentralized applications and smart contracts across platforms. However, this often involves a custodian or smart contract to hold the original asset securely, introducing centralization and counterparty risk. Not original or primary blockchain the token is hosted Non-native networks Choose networks @@ -549,6 +551,9 @@ Access code restore Identical cards Access code + Our services are not available in this country + Change or confirm it + Your residence has been identified as Group By balance Organize tokens @@ -705,7 +710,7 @@ Market rating Metrics Minimum Requirement - No rewards + No rewards available Reward claiming Method of receiving staking rewards.\nIt can be either automatic, where the reward is credited to your address, or manual, where you need to withdraw the reward by creating a transaction to receive it. Reward schedule @@ -716,6 +721,7 @@ The period you must wait after requesting to withdraw funds from staking before the tokens become available. Warmup period The allocated time for activating participation in staking. + The network will charge a token approval fee to verify that you are authorizing the use of your token for the staking. By using staking functionality, you agree with provider’s %1$s and %2$s Locked Migrate @@ -754,6 +760,8 @@ Daily Each %s Each day + Each %s days + Each Per Epoch Per Era Hourly @@ -762,7 +770,7 @@ Rewards Stake locked Stake more - You stake %1$s and will be receiving your reward each %2$s + You stake %1$s and will be receiving your reward %2$s Tap to unlock Tap to unlock or vote Tap to withdraw @@ -772,6 +780,7 @@ Unbonding Unlock Unlocking + Amount exceeds staked balance Unstaked Unstaking Unstaking assets %s @@ -793,6 +802,7 @@ Meet Tangem Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services Web 3.0 Compatible + The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap. Exchange more tokens at better rates directly in your wallet. New Swap Provider Available! The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt index 13462984c2..0de3e1e864 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/currency/fiaticon/FiatIcon.kt @@ -24,7 +24,7 @@ fun FiatIcon( size: Dp, isGrayscale: Boolean, modifier: Modifier = Modifier, - @DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle, + @DrawableRes fallbackResId: Int = R.drawable.ic_shape_circle_40, ) { val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url val alpha = if (isGrayscale) GRAY_SCALE_ALPHA else 1f diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt index c7a5af394e..efcf847a05 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/TextReference.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString.Builder import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.decapitalize import org.intellij.markdown.MarkdownElementTypes import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @@ -26,11 +27,16 @@ sealed interface TextReference { /** * Text resource id * - * @property id resource id - * @property formatArgs arguments. Impossible to use [kotlinx.collections.immutable.ImmutableList] because [Any] is - * unstable. + * @property id resource id + * @property formatArgs arguments. Impossible to use [kotlinx.collections.immutable.ImmutableList] because + * [Any] is unstable. + * @property decapitalize whether resolved reference should be decapitalized */ - data class Res(@StringRes val id: Int, val formatArgs: WrappedList = WrappedList(emptyList())) : TextReference + data class Res( + @StringRes val id: Int, + val formatArgs: WrappedList = WrappedList(emptyList()), + val decapitalize: Boolean = false, + ) : TextReference /** * Plural resource id @@ -78,10 +84,15 @@ sealed interface TextReference { * * @param id The resource ID of the string. * @param formatArgs A list of format arguments to be applied to the string resource. - * @return A [TextReference] representing the string resource with format arguments. + * @param decapitalize Whether resolved reference should be decapitalized + * @return [TextReference] representing the string resource with format arguments. */ -fun resourceReference(@StringRes id: Int, formatArgs: WrappedList = WrappedList(emptyList())): TextReference { - return TextReference.Res(id, formatArgs) +fun resourceReference( + @StringRes id: Int, + formatArgs: WrappedList = WrappedList(emptyList()), + decapitalize: Boolean = false, +): TextReference { + return TextReference.Res(id, formatArgs, decapitalize) } /** @@ -161,7 +172,13 @@ fun TextReference.resolveReference(): String { .map { if (it is TextReference) it.resolveReference() else it } .toTypedArray() - stringResource(id = id, *args) + val resolvedReference = stringResource(id = id, *args) + + if (decapitalize) { + resolvedReference.replaceFirstChar { char -> char.lowercase() } + } else { + resolvedReference + } } is TextReference.PluralRes -> pluralStringResource(id, count, *formatArgs.toTypedArray()) is TextReference.Str -> value diff --git a/core/ui/src/main/res/drawable/ic_shape_circle.xml b/core/ui/src/main/res/drawable/ic_shape_circle.xml deleted file mode 100644 index 59a662d141..0000000000 --- a/core/ui/src/main/res/drawable/ic_shape_circle.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_shape_circle_40.xml b/core/ui/src/main/res/drawable/ic_shape_circle_40.xml new file mode 100644 index 0000000000..d618196f7b --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_shape_circle_40.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/utils/src/main/java/com/tangem/utils/SupportedLanguages.kt b/core/utils/src/main/java/com/tangem/utils/SupportedLanguages.kt index 48b2788b37..b85a35b477 100644 --- a/core/utils/src/main/java/com/tangem/utils/SupportedLanguages.kt +++ b/core/utils/src/main/java/com/tangem/utils/SupportedLanguages.kt @@ -11,6 +11,7 @@ object SupportedLanguages { const val JAPANESE = "ja" const val UKRAINIAN = "uk" const val CHINESE = "uk" + const val SPANISH = "es" val supportedLangugeCodes = listOf( ENGLISH, @@ -21,6 +22,7 @@ object SupportedLanguages { JAPANESE, UKRAINIAN, CHINESE, + SPANISH, ) fun getCurrentSupportedLanguageCode(): String { diff --git a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt index a62a1be508..70c7c69396 100644 --- a/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt +++ b/data/manage-tokens/src/main/kotlin/com/tangem/data/managetokens/DefaultManageTokensRepository.kt @@ -99,6 +99,7 @@ internal class DefaultManageTokensRepository( loadUserTokensFromRemote: Boolean, ): BatchFetchResult.Success> { val supportedBlockchains = getSupportedBlockchains(userWallet) + val query = request.params.searchText.takeUnless(String?::isNullOrBlank) val call = suspend { tangemTechApi.getCoins( @@ -107,7 +108,7 @@ internal class DefaultManageTokensRepository( transform = Blockchain::toNetworkId, ), active = true, - searchText = request.params.searchText, + searchText = query, offset = request.offset * request.limit, limit = request.limit, ).getOrThrow() @@ -135,7 +136,7 @@ internal class DefaultManageTokensRepository( val items = if (isFirstBatchFetching && tokensResponse != null && userWallet != null && - request.params.searchText.isNullOrBlank() + query == null ) { managedCryptoCurrencyFactory.createWithCustomTokens( coinsResponse = updatedCoinsResponse, diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketExchangeConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketExchangeConverter.kt index 94aae7673a..4660b28ef9 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketExchangeConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketExchangeConverter.kt @@ -1,6 +1,7 @@ package com.tangem.data.markets.converters import com.tangem.datasource.api.markets.models.response.TokenMarketExchangesResponse +import com.tangem.domain.markets.BuildConfig import com.tangem.domain.markets.TokenMarketExchange import com.tangem.utils.converter.Converter @@ -11,6 +12,8 @@ import com.tangem.utils.converter.Converter */ internal object TokenMarketExchangeConverter : Converter { + private const val PROD_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api/exchanges/" + private const val DEV_IMAGE_HOST = "https://s3.eu-central-1.amazonaws.com/tangem.api.dev/exchanges/" private val RISKY_INTERVAL = 0..3 private val CAUTION_INTERVAL = 4..7 @@ -19,7 +22,7 @@ internal object TokenMarketExchangeConverter : Converter StakingError.GRTStakingDisabledLedgerLiveError - else -> StakingError.UnknownError + else -> StakingError.UnknownError(value) } } catch (e: Exception) { - StakingError.UnknownError + StakingError.UnknownError(value) } } } \ No newline at end of file diff --git a/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt index 130141ab85..5ebe3d67f6 100644 --- a/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt +++ b/domain/demo/src/main/java/com/tangem/domain/demo/DemoTransactionSender.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionSendResult +import com.tangem.blockchain.common.transaction.TransactionsSendResult import com.tangem.blockchain.extensions.Result import com.tangem.common.CompletionResult import kotlin.random.Random @@ -39,6 +40,11 @@ class DemoTransactionSender(private val walletManager: WalletManager) : Transact } } + override suspend fun sendMultiple( + transactionDataList: List, + signer: TransactionSigner, + ): Result = Result.Failure(Exception(ID).toBlockchainSdkError()) + private fun getDataToSign(): ByteArray { val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') return IntRange(start = 1, endInclusive = 32) diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt index ffbfa795bf..79a0481bc4 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/ValidateTokenFormUseCase.kt @@ -16,12 +16,19 @@ class ValidateTokenFormUseCase( networkId: Network.ID, formValues: AddCustomTokenForm.Raw, ): EitherNel = either { - if (formValues.name.isBlank() && formValues.symbol.isBlank() && formValues.decimals.isBlank()) { - val contractAddress = withError({ nonEmptyListOf(it) }) { - ensureIsContractAddressValid(formValues.contractAddress, networkId) - } + if (formValues.name.isBlank() && + formValues.symbol.isBlank() && + formValues.decimals.isBlank() + ) { + if (formValues.contractAddress.isBlank()) { + AddCustomTokenForm.Validated.Empty + } else { + val contractAddress = withError({ nonEmptyListOf(it) }) { + ensureIsContractAddressValid(formValues.contractAddress, networkId) + } - AddCustomTokenForm.Validated.ContractAddress(contractAddress) + AddCustomTokenForm.Validated.ContractAddressOnly(contractAddress) + } } else { zipOrAccumulate( { ensureIsContractAddressValid(formValues.contractAddress, networkId) }, @@ -43,10 +50,6 @@ class ValidateTokenFormUseCase( contractAddress: String, networkId: Network.ID, ): String { - ensure(contractAddress.isNotBlank()) { - CustomTokenFormValidationException.ContractAddress.Empty - } - val isValid = catch({ repository.validateContractAddress(contractAddress, networkId) }) { raise(CustomTokenFormValidationException.DataError(it)) } diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/AddCustomTokenForm.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/AddCustomTokenForm.kt index a5b5dc5bdc..c2cebdb84e 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/AddCustomTokenForm.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/AddCustomTokenForm.kt @@ -11,7 +11,9 @@ sealed class AddCustomTokenForm { sealed class Validated : AddCustomTokenForm() { - data class ContractAddress( + data object Empty : Validated() + + data class ContractAddressOnly( val contractAddress: String, ) : Validated() diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/CustomTokenFormValidationException.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/CustomTokenFormValidationException.kt index a197980215..37781b95e1 100644 --- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/CustomTokenFormValidationException.kt +++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/model/exceptoin/CustomTokenFormValidationException.kt @@ -4,8 +4,6 @@ sealed class CustomTokenFormValidationException { sealed class ContractAddress : CustomTokenFormValidationException() { - data object Empty : ContractAddress() - data object Invalid : ContractAddress() } diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/StakingError.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/StakingError.kt index 9fc68971f3..b02b33741f 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/StakingError.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/StakingError.kt @@ -92,5 +92,5 @@ sealed class StakingError { data class DataError(val cause: Throwable) : StakingError() - data object UnknownError : StakingError() + data class UnknownError(val message: String? = null) : StakingError() } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt index 6dd0f53745..2c07ae8ea0 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -42,7 +42,8 @@ class InvalidatePendingTransactionsUseCase( newBalancesId: Int, pendingData: List>, ): Pair, List> { - val balances = realData.associateBy { BalanceIdentity(it.groupId, it.type, it.amount, it.date) } + val balances = realData.groupBy { BalanceIdentity(it.groupId, it.type, it.amount, it.date) } + .mapValues { it.value.toMutableList() } .toMutableMap() val transactionsToRemove = mutableListOf() @@ -56,28 +57,34 @@ class InvalidatePendingTransactionsUseCase( transactionsToRemove.add(pendingTransaction) } balances.containsKey(key) -> { - balances[key] = balanceItem + val removed = balances[key]?.removeIf { !it.isPending } ?: false + if (removed) { + balances[key]?.add(balanceItem) + } } else -> { val groupId = UUID.randomUUID().toString() val now = DateTime.now() + balances[BalanceIdentity(groupId, BalanceType.STAKED, pendingTransaction.amount, now)] = - BalanceItem( - groupId = groupId, - type = pendingTransaction.type, - amount = pendingTransaction.amount, - rawCurrencyId = pendingTransaction.rawCurrencyId, - validatorAddress = pendingTransaction.validator?.address, - date = null, - pendingActions = emptyList(), - token = pendingTransaction.token, - isPending = true, + mutableListOf( + BalanceItem( + groupId = groupId, + type = pendingTransaction.type, + amount = pendingTransaction.amount, + rawCurrencyId = pendingTransaction.rawCurrencyId, + validatorAddress = pendingTransaction.validator?.address, + date = null, + pendingActions = emptyList(), + token = pendingTransaction.token, + isPending = true, + ), ) balances.entries.find { it.key.type == BalanceType.STAKED && it.key.amount == pendingTransaction.amount && - !it.value.isPending + !it.value.any { it.isPending } } ?.key ?.let { balances.remove(it) } @@ -85,7 +92,7 @@ class InvalidatePendingTransactionsUseCase( } } - return balances.values.toList() to transactionsToRemove + return balances.values.flatten() to transactionsToRemove } private data class BalanceIdentity( diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt index f6c43189f7..b0b7f781d9 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt @@ -63,15 +63,9 @@ internal class CustomTokenFormModel @Inject constructor( if (params.network.canHandleTokens) { observeTokenFormUpdates() - } else { - modelScope.launch { - customCurrencyValidator.createCoin( - userWalletId = params.userWalletId, - networkId = params.network.id, - derivationPath = getDerivationPath(), - ) - } } + + createCoin() } private fun getInitialState(): CustomTokenFormUM { @@ -114,6 +108,7 @@ internal class CustomTokenFormModel @Inject constructor( } .distinctUntilChanged() .sample(periodMillis = 1_000) + .drop(count = 1) // Skip initial state .onEach { formValues -> customCurrencyValidator.validateForm( userWalletId = params.userWalletId, @@ -153,6 +148,14 @@ internal class CustomTokenFormModel @Inject constructor( } } + private fun createCoin() = modelScope.launch { + customCurrencyValidator.createCoin( + userWalletId = params.userWalletId, + networkId = params.network.id, + derivationPath = getDerivationPath(), + ) + } + private fun updateStateWithCurrency( currency: CryptoCurrency, fillForm: Boolean, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt index 685849ec59..d5ba7409da 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyValidator.kt @@ -75,9 +75,12 @@ internal class CustomCurrencyValidator @Inject constructor( is AddCustomTokenForm.Validated.All -> { findOrCreateCurrency(userWalletId, networkId, derivationPath, validatedForm) } - is AddCustomTokenForm.Validated.ContractAddress -> { + is AddCustomTokenForm.Validated.ContractAddressOnly -> { findToken(userWalletId, networkId, derivationPath, validatedForm) } + is AddCustomTokenForm.Validated.Empty -> { + createCoin(userWalletId, networkId, derivationPath) + } } }.saveInAndJoin(validateFormJobHolder) } @@ -132,7 +135,7 @@ internal class CustomCurrencyValidator @Inject constructor( userWalletId: UserWalletId, networkId: Network.ID, derivationPath: Network.DerivationPath, - validatedForm: AddCustomTokenForm.Validated.ContractAddress, + validatedForm: AddCustomTokenForm.Validated.ContractAddressOnly, ) { updateStatus(Status.SearchingToken) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt index 147642b2bf..b2e1f4fbdb 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt @@ -96,9 +96,6 @@ internal fun CustomTokenFormUM.updateWithContractAddressException( val updatedFields = fields.mutate { it[Field.CONTRACT_ADDRESS] = it.getValue(Field.CONTRACT_ADDRESS).copy( error = when (exception) { - CustomTokenFormValidationException.ContractAddress.Empty -> { - null // Should not display this error - } CustomTokenFormValidationException.ContractAddress.Invalid -> { resourceReference(R.string.custom_token_creation_error_invalid_contract_address) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt index 3300e07216..28f8360933 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt @@ -526,9 +526,12 @@ internal class MarketsTokenDetailsModel @Inject constructor( } private fun onListedOnClick(exchangesCount: Int) { - showBottomSheet(content = ExchangesBottomSheetContent.Loading(exchangesCount)) - modelScope.launch { + showBottomSheet(content = ExchangesBottomSheetContent.Loading(exchangesCount)) + + // Delay to show the bottom sheet + delay(timeMillis = 800L) + val maybeExchanges = getTokenExchangesUseCase(tokenId = params.token.id) updateExchangeBSContent(maybeExchanges = maybeExchanges, exchangesCount = exchangesCount) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ExchangesBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ExchangesBottomSheet.kt index e791211b63..5e2a7e2973 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ExchangesBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ExchangesBottomSheet.kt @@ -3,11 +3,10 @@ package com.tangem.features.markets.details.impl.ui.components import android.content.res.Configuration import androidx.annotation.StringRes import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity @@ -17,9 +16,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEach -import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM @@ -57,44 +53,37 @@ internal fun ExchangesBottomSheet(config: TangemBottomSheetConfig) { addBottomInsets = false, title = { Title(textResId = it.titleResId, onBackClick = config.onDismissRequest) }, content = { content -> - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(state = rememberScrollState()), - ) { - Subtitle( - subtitleRes = content.subtitleResId, - volumeReference = content.volumeReference, - modifier = Modifier.padding( - start = TangemTheme.dimens.spacing16, - top = TangemTheme.dimens.spacing12, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing8, - ), - ) - - when (content) { - is ExchangesBottomSheetContent.Content, - is ExchangesBottomSheetContent.Loading, - -> { - content.exchangeItems.fastForEach { item -> - key(item.id) { - TokenItem(state = item, isBalanceHidden = false) - } - } - } - is ExchangesBottomSheetContent.Error -> { - Error( - content = content, - modifier = Modifier - .align(alignment = Alignment.CenterHorizontally) - .padding(horizontal = 16.dp) - .weight(1f), + Box { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = bottomBarHeight), + ) { + item(key = "subtitle") { + Subtitle( + subtitleRes = content.subtitleResId, + volumeReference = content.volumeReference, + modifier = Modifier.padding( + start = TangemTheme.dimens.spacing16, + top = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing8, + ), ) } + + items( + items = content.exchangeItems, + key = TokenItemState::id, + itemContent = { TokenItem(state = it, isBalanceHidden = false) }, + ) } - SpacerH(bottomBarHeight) + if (content is ExchangesBottomSheetContent.Error) { + Error( + content = content, + modifier = Modifier.align(Alignment.Center), + ) + } } }, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ListedOnBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ListedOnBlock.kt index 2ba168b352..3d5714135e 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ListedOnBlock.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/ListedOnBlock.kt @@ -10,6 +10,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -33,7 +34,7 @@ import kotlinx.coroutines.delay */ @Composable internal fun ListedOnBlock(state: ListedOnUM, modifier: Modifier = Modifier) { - Box { + Box(modifier = modifier) { InformationBlock( title = { Text( @@ -44,9 +45,11 @@ internal fun ListedOnBlock(state: ListedOnUM, modifier: Modifier = Modifier) { overflow = TextOverflow.Ellipsis, ) }, - modifier = modifier.clickable(enabled = state is ListedOnUM.Content) { - (state as? ListedOnUM.Content)?.onClick?.invoke() - }, + modifier = Modifier + .clip(shape = TangemTheme.shapes.roundedCornersXMedium) + .clickable(enabled = state is ListedOnUM.Content) { + (state as? ListedOnUM.Content)?.onClick?.invoke() + }, ) { Description( state = state, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt index 1bf9b9ed01..23f483ef38 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/confirm/SendNotificationFactory.kt @@ -93,7 +93,7 @@ internal class SendNotificationFactory constructor( val currencyCheck = getCurrencyCheckUseCase( userWalletId = userWalletId, currencyStatus = cryptoCurrencyStatus, - amount = amountValue, + amount = sendingAmount, fee = feeValue, ) buildList { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt index 449c2f6f43..4c557fecbb 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/FeeConverter.kt @@ -72,10 +72,7 @@ internal class FeeConverter( normalFee } else { when (normalFee) { - is Fee.Ethereum.Legacy -> ethereumCustomFeeConverter.convertBack( - normalFee = normalFee, - value = customValues, - ) + is Fee.Ethereum -> ethereumCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues) is Fee.Bitcoin -> bitcoinCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues) is Fee.Kaspa -> kaspaCustomFeeConverter.convertBack(normalFee = normalFee, value = customValues) else -> { diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt index 25849d36f7..16a8666b36 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/SendFeeCustomFieldConverter.kt @@ -49,7 +49,7 @@ internal class SendFeeCustomFieldConverter( override fun convert(value: Fee): ImmutableList { return when (value) { - is Fee.Ethereum.Legacy -> ethereumCustomFeeConverter.convert(value) + is Fee.Ethereum -> ethereumCustomFeeConverter.convert(value) is Fee.Bitcoin -> bitcoinCustomFeeConverter.convert(value) is Fee.Kaspa -> kaspaCustomFeeConverter.convert(value) else -> persistentListOf() @@ -59,6 +59,7 @@ internal class SendFeeCustomFieldConverter( fun onValueChange(feeSelectorState: FeeSelectorState.Content, index: Int, value: String) = feeSelectorState.copy( customValues = when (val fee = feeSelectorState.fees.normal) { is Fee.Ethereum -> ethereumCustomFeeConverter.onValueChange( + feeValue = fee, customValues = feeSelectorState.customValues, index = index, value = value, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BaseEthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BaseEthereumCustomFeeConverter.kt new file mode 100644 index 0000000000..3956b35bbd --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/BaseEthereumCustomFeeConverter.kt @@ -0,0 +1,24 @@ +package com.tangem.features.send.impl.presentation.state.fee.custom + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import kotlinx.collections.immutable.ImmutableList + +/** + * Base ethereum custom fee converter + * + * @param T subtype of [Fee.Ethereum] + * +[REDACTED_AUTHOR] + */ +internal interface BaseEthereumCustomFeeConverter : CustomFeeConverter { + + fun getGasLimitIndex(feeValue: T): Int + + fun onValueChange( + feeValue: T, + customValues: ImmutableList, + index: Int, + value: String, + ): ImmutableList +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt index b44dc65a5b..b65735aeb2 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumCustomFeeConverter.kt @@ -8,7 +8,6 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.utils.getFiatReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.utils.parseBigDecimal -import com.tangem.core.ui.utils.parseToBigDecimal import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.features.send.impl.R @@ -18,203 +17,119 @@ import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import com.tangem.utils.Provider import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import java.math.RoundingMode internal class EthereumCustomFeeConverter( private val clickIntents: SendClickIntents, private val stateRouterProvider: Provider, private val appCurrencyProvider: Provider, private val feeCryptoCurrencyStatusProvider: Provider, -) : CustomFeeConverter { +) : BaseEthereumCustomFeeConverter { - override fun convert(value: Fee.Ethereum.Legacy): ImmutableList { - val feeValue = value.amount.value - val feeCurrency = feeCryptoCurrencyStatusProvider()?.value - return persistentListOf( - SendTextField.CustomFee( - value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), - decimals = value.amount.decimals, - symbol = value.amount.currencySymbol, - onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT, it) }, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Number, - ), - title = resourceReference(R.string.send_max_fee), - footer = resourceReference(R.string.send_custom_amount_fee_footer), - label = getFiatReference( - rate = feeCurrency?.fiatRate, - value = feeValue, - appCurrency = appCurrencyProvider(), - ), - keyboardActions = KeyboardActions(), - ), - SendTextField.CustomFee( - value = value.gasPrice.toBigDecimal().movePointLeft(GIGA_DECIMALS).parseBigDecimal(GIGA_DECIMALS), - decimals = GIGA_DECIMALS, - symbol = ETHEREUM_GAS_UNIT, - title = resourceReference(R.string.send_gas_price), - footer = resourceReference(R.string.send_gas_price_footer), - onValueChange = { clickIntents.onCustomFeeValueChange(GAS_PRICE, it) }, - keyboardOptions = KeyboardOptions( - imeAction = ImeAction.Next, - keyboardType = KeyboardType.Number, - ), - keyboardActions = KeyboardActions(), - ), - SendTextField.CustomFee( - value = value.gasLimit.toString(), - decimals = GAS_DECIMALS, - symbol = "", - title = resourceReference(R.string.send_gas_limit), - footer = resourceReference(R.string.send_gas_limit_footer), - onValueChange = { clickIntents.onCustomFeeValueChange(GAS_LIMIT, it) }, - keyboardOptions = KeyboardOptions( - imeAction = if ( - checkExceedBalance( - feeBalance = feeCurrency?.amount, - feeAmount = feeValue, - ) - ) { - ImeAction.None - } else { - ImeAction.Done - }, - keyboardType = KeyboardType.Number, - ), - keyboardActions = KeyboardActions( - onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, - ), - ), - ) + private val feeCurrency: CryptoCurrencyStatus.Value? + get() = feeCryptoCurrencyStatusProvider()?.value + + private val legacyFeeConverter = EthereumLegacyCustomFeeConverter( + clickIntents = clickIntents, + appCurrencyProvider = appCurrencyProvider, + feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, + ) + + private val eipFeeConverter = EthereumEIPCustomFeeConverter( + clickIntents = clickIntents, + appCurrencyProvider = appCurrencyProvider, + feeCryptoCurrencyStatusProvider = feeCryptoCurrencyStatusProvider, + ) + + override fun convert(value: Fee.Ethereum): ImmutableList { + return buildList { + convertFeeValue(value).let(::add) + + when (value) { + is Fee.Ethereum.EIP1559 -> eipFeeConverter.convert(value) + is Fee.Ethereum.Legacy -> legacyFeeConverter.convert(value) + } + .let(::addAll) + + convertGasLimitValue(value).let(::add) + } + .toImmutableList() } - override fun convertBack( - normalFee: Fee.Ethereum.Legacy, - value: ImmutableList, - ): Fee.Ethereum.Legacy { - val feeAmount = value[FEE_AMOUNT].value.parseToBigDecimal(value[FEE_AMOUNT].decimals) - val gasPrice = value[GAS_PRICE].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() - val gasLimit = value[GAS_LIMIT].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() - return normalFee.copy( - amount = normalFee.amount.copy(value = feeAmount), - gasPrice = gasPrice, - gasLimit = gasLimit, - ) + override fun convertBack(normalFee: Fee.Ethereum, value: ImmutableList): Fee.Ethereum { + return when (normalFee) { + is Fee.Ethereum.EIP1559 -> eipFeeConverter.convertBack(normalFee = normalFee, value = value) + is Fee.Ethereum.Legacy -> legacyFeeConverter.convertBack(normalFee = normalFee, value = value) + } } - fun onValueChange( + override fun getGasLimitIndex(feeValue: Fee.Ethereum): Int { + return when (feeValue) { + is Fee.Ethereum.EIP1559 -> eipFeeConverter.getGasLimitIndex(feeValue) + is Fee.Ethereum.Legacy -> legacyFeeConverter.getGasLimitIndex(feeValue) + } + } + + override fun onValueChange( + feeValue: Fee.Ethereum, customValues: ImmutableList, index: Int, value: String, ): ImmutableList { - val mutableCustomValues = customValues.toMutableList() - return mutableCustomValues.apply { - when (index) { - FEE_AMOUNT -> setOnAmountChange(value, index) - GAS_PRICE -> setOnGasPriceChange(value, index) - else -> setOnGasLimitChange(value, index) - } - }.toImmutableList() - } - - private fun MutableList.setEmpty(index: Int) { - set(index, this[index].copy(value = "")) - } - - private fun MutableList.setOnAmountChange(value: String, index: Int) { - val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(GAS_PRICE) - } else { - val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals) - val newFeeAmount = newFeeAmountDecimal.movePointRight(this[GAS_PRICE].decimals) // from ETH to GWEI - val newGasPrice = newFeeAmount.divide(gasLimit, this[GAS_PRICE].decimals, RoundingMode.HALF_UP) - set(GAS_PRICE, this[GAS_PRICE].copy(value = newGasPrice.parseBigDecimal(this[GAS_PRICE].decimals))) - set( - index, - this[index].copy( - value = value, - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmountDecimal, - appCurrency = appCurrencyProvider(), - ), - ), - ) + return when (feeValue) { + is Fee.Ethereum.EIP1559 -> eipFeeConverter.onValueChange(feeValue, customValues, index, value) + is Fee.Ethereum.Legacy -> legacyFeeConverter.onValueChange(feeValue, customValues, index, value) } } - private fun MutableList.setOnGasPriceChange(value: String, index: Int) { - val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(GAS_PRICE) - } else { - val newGasPrice = value.parseToBigDecimal(this[GAS_PRICE].decimals) - .movePointLeft(this[GAS_PRICE].decimals) // from GWEI to ETH - val newFeeAmount = gasLimit * newGasPrice - set( - FEE_AMOUNT, - this[FEE_AMOUNT].copy( - value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmount, - appCurrency = appCurrencyProvider(), - ), - ), - ) - set(index, this[index].copy(value = value)) - } + private fun convertFeeValue(value: Fee.Ethereum): SendTextField.CustomFee { + val feeValue = value.amount.value + + return SendTextField.CustomFee( + value = feeValue?.parseBigDecimal(value.amount.decimals).orEmpty(), + decimals = value.amount.decimals, + symbol = value.amount.currencySymbol, + onValueChange = { clickIntents.onCustomFeeValueChange(FEE_AMOUNT, it) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = KeyboardType.Number), + title = resourceReference(R.string.send_max_fee), + footer = resourceReference(R.string.send_custom_amount_fee_footer), + label = getFiatReference( + rate = feeCurrency?.fiatRate, + value = feeValue, + appCurrency = appCurrencyProvider(), + ), + keyboardActions = KeyboardActions(), + ) } - private fun MutableList.setOnGasLimitChange(value: String, index: Int) { - if (value.isBlank()) { - setEmpty(FEE_AMOUNT) - setEmpty(GAS_LIMIT) - } else { - val newGasLimit = value.parseToBigDecimal(this[GAS_LIMIT].decimals) - val gasPrice = this[GAS_PRICE].value.parseToBigDecimal(this[GAS_PRICE].decimals) - .movePointLeft(this[GAS_PRICE].decimals) // from GWEI to ETH - val newFeeAmount = newGasLimit * gasPrice - set( - FEE_AMOUNT, - this[FEE_AMOUNT].copy( - value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), - label = getFiatReference( - rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, - value = newFeeAmount, - appCurrency = appCurrencyProvider(), - ), - ), - ) - val isNotExceedBalance = checkExceedBalance( - feeBalance = feeCryptoCurrencyStatusProvider()?.value?.amount, - feeAmount = newFeeAmount, - ) - set( - index, - this[index].copy( - value = value, - keyboardOptions = KeyboardOptions( - imeAction = if (!isNotExceedBalance) ImeAction.None else ImeAction.Done, - keyboardType = KeyboardType.Number, - ), - ), - ) - } + private fun convertGasLimitValue(value: Fee.Ethereum): SendTextField.CustomFee { + val isExceedBalance = checkExceedBalance(feeBalance = feeCurrency?.amount, feeAmount = value.amount.value) + + return SendTextField.CustomFee( + value = value.gasLimit.toString(), + decimals = GAS_DECIMALS, + symbol = "", + title = resourceReference(R.string.send_gas_limit), + footer = resourceReference(R.string.send_gas_limit_footer), + onValueChange = { clickIntents.onCustomFeeValueChange(getGasLimitIndex(value), it) }, + keyboardOptions = KeyboardOptions( + imeAction = if (isExceedBalance) ImeAction.None else ImeAction.Done, + keyboardType = KeyboardType.Number, + ), + keyboardActions = KeyboardActions( + onDone = { clickIntents.onNextClick(stateRouterProvider().isEditState) }, + ), + ) } companion object { - private const val ETHEREUM_GAS_UNIT = "GWEI" - private const val GIGA_DECIMALS = 9 - private const val FEE_AMOUNT = 0 - private const val GAS_PRICE = 1 - private const val GAS_LIMIT = 2 - private const val GAS_DECIMALS = 0 + const val ETHEREUM_GAS_UNIT = "GWEI" + const val GIGA_DECIMALS = 9 + const val GAS_DECIMALS = 0 + const val FEE_AMOUNT = 0 } +} + +internal fun MutableList.setEmpty(index: Int) { + set(index, this[index].copy(value = "")) } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt new file mode 100644 index 0000000000..a3d874d526 --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumEIPCustomFeeConverter.kt @@ -0,0 +1,196 @@ +package com.tangem.features.send.impl.presentation.state.fee.custom + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.common.ui.amountScreen.utils.getFiatReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance +import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.ETHEREUM_GAS_UNIT +import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.FEE_AMOUNT +import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GAS_DECIMALS +import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS +import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.utils.Provider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import java.math.RoundingMode + +internal class EthereumEIPCustomFeeConverter( + private val clickIntents: SendClickIntents, + private val appCurrencyProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, +) : BaseEthereumCustomFeeConverter { + + override fun convert(value: Fee.Ethereum.EIP1559): ImmutableList { + return persistentListOf( + SendTextField.CustomFee( + value = value.maxFeePerGas.toBigDecimal().movePointLeft(GIGA_DECIMALS).parseBigDecimal(GIGA_DECIMALS), + decimals = GIGA_DECIMALS, + symbol = ETHEREUM_GAS_UNIT, + title = resourceReference(R.string.send_custom_evm_max_fee), + footer = resourceReference(R.string.send_custom_evm_max_fee_footer), + onValueChange = { clickIntents.onCustomFeeValueChange(MAX_FEE, it) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = KeyboardType.Number), + keyboardActions = KeyboardActions(), + ), + SendTextField.CustomFee( + value = value.priorityFee.toBigDecimal().movePointLeft(GIGA_DECIMALS).parseBigDecimal(GIGA_DECIMALS), + decimals = GIGA_DECIMALS, + symbol = ETHEREUM_GAS_UNIT, + title = resourceReference(R.string.send_custom_evm_priority_fee), + footer = resourceReference(R.string.send_custom_evm_priority_fee_footer), + onValueChange = { clickIntents.onCustomFeeValueChange(PRIORITY_FEE, it) }, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next, keyboardType = KeyboardType.Number), + keyboardActions = KeyboardActions(), + ), + ) + } + + override fun convertBack( + normalFee: Fee.Ethereum.EIP1559, + value: ImmutableList, + ): Fee.Ethereum.EIP1559 { + val feeAmount = value[FEE_AMOUNT].value.parseToBigDecimal(value[FEE_AMOUNT].decimals) + val maxFee = value[MAX_FEE].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() + val priorityFee = value[PRIORITY_FEE].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() + val gasLimit = value[GAS_LIMIT].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() + + return normalFee.copy( + amount = normalFee.amount.copy(value = feeAmount), + maxFeePerGas = maxFee, + priorityFee = priorityFee, + gasLimit = gasLimit, + ) + } + + override fun getGasLimitIndex(feeValue: Fee.Ethereum.EIP1559): Int = GAS_LIMIT + + override fun onValueChange( + feeValue: Fee.Ethereum.EIP1559, + customValues: ImmutableList, + index: Int, + value: String, + ): ImmutableList { + val mutableCustomValues = customValues.toMutableList() + return mutableCustomValues.apply { + when (index) { + FEE_AMOUNT -> setOnAmountChange(value, index) + MAX_FEE -> setOnMaxFeeChange(value, index) + GAS_LIMIT -> setOnGasLimitChange(value, index) + else -> set(index, this[index].copy(value = value)) + } + }.toImmutableList() + } + + private fun MutableList.setOnAmountChange(value: String, index: Int) { + val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) + if (value.isBlank()) { + setEmpty(FEE_AMOUNT) + setEmpty(MAX_FEE) + } else { + val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals) + val newFeeAmount = newFeeAmountDecimal.movePointRight(GIGA_DECIMALS) // from ETH to GWEI + + val newMaxFee = newFeeAmount.divide(gasLimit, this[MAX_FEE].decimals, RoundingMode.HALF_UP) + + set( + index = MAX_FEE, + element = this[MAX_FEE].copy(value = newMaxFee.parseBigDecimal(this[MAX_FEE].decimals)), + ) + + set( + index = index, + element = this[index].copy( + value = value, + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmountDecimal, + appCurrency = appCurrencyProvider(), + ), + ), + ) + } + } + + private fun MutableList.setOnMaxFeeChange(value: String, index: Int) { + val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) + + if (value.isBlank()) { + setEmpty(FEE_AMOUNT) + setEmpty(MAX_FEE) + } else { + val newMaxFee = value.parseToBigDecimal(this[MAX_FEE].decimals).movePointLeft(this[MAX_FEE].decimals) + val newFeeAmount = gasLimit * newMaxFee + set( + FEE_AMOUNT, + this[FEE_AMOUNT].copy( + value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), + ), + ) + set(index, this[index].copy(value = value)) + } + } + + private fun MutableList.setOnGasLimitChange(value: String, index: Int) { + if (value.isBlank()) { + setEmpty(FEE_AMOUNT) + setEmpty(GAS_LIMIT) + } else { + val newGasLimit = value.parseToBigDecimal(this[GAS_LIMIT].decimals) + + val maxFee = this[MAX_FEE].value.parseToBigDecimal(this[MAX_FEE].decimals) + .movePointLeft(this[MAX_FEE].decimals) // from GWEI to ETH + + val newFeeAmount = newGasLimit * maxFee + + set( + index = FEE_AMOUNT, + element = this[FEE_AMOUNT].copy( + value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), + ), + ) + + val isNotExceedBalance = checkExceedBalance( + feeBalance = feeCryptoCurrencyStatusProvider()?.value?.amount, + feeAmount = newFeeAmount, + ) + + set( + index = index, + element = this[index].copy( + value = value, + keyboardOptions = KeyboardOptions( + imeAction = if (!isNotExceedBalance) ImeAction.None else ImeAction.Done, + keyboardType = KeyboardType.Number, + ), + ), + ) + } + } + + private companion object { + const val MAX_FEE = 1 + const val PRIORITY_FEE = 2 + const val GAS_LIMIT = 3 + } +} \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt new file mode 100644 index 0000000000..b4058dd76d --- /dev/null +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/state/fee/custom/EthereumLegacyCustomFeeConverter.kt @@ -0,0 +1,179 @@ +package com.tangem.features.send.impl.presentation.state.fee.custom + +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.common.ui.amountScreen.utils.getFiatReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.utils.parseBigDecimal +import com.tangem.core.ui.utils.parseToBigDecimal +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.features.send.impl.R +import com.tangem.features.send.impl.presentation.state.fee.checkExceedBalance +import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.ETHEREUM_GAS_UNIT +import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.FEE_AMOUNT +import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GAS_DECIMALS +import com.tangem.features.send.impl.presentation.state.fee.custom.EthereumCustomFeeConverter.Companion.GIGA_DECIMALS +import com.tangem.features.send.impl.presentation.state.fields.SendTextField +import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents +import com.tangem.utils.Provider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList +import java.math.RoundingMode + +internal class EthereumLegacyCustomFeeConverter( + private val clickIntents: SendClickIntents, + private val appCurrencyProvider: Provider, + private val feeCryptoCurrencyStatusProvider: Provider, +) : BaseEthereumCustomFeeConverter { + + override fun convert(value: Fee.Ethereum.Legacy): ImmutableList { + return persistentListOf( + SendTextField.CustomFee( + value = value.gasPrice.toBigDecimal().movePointLeft(GIGA_DECIMALS).parseBigDecimal(GIGA_DECIMALS), + decimals = GIGA_DECIMALS, + symbol = ETHEREUM_GAS_UNIT, + title = resourceReference(R.string.send_gas_price), + footer = resourceReference(R.string.send_gas_price_footer), + onValueChange = { clickIntents.onCustomFeeValueChange(GAS_PRICE, it) }, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Next, + keyboardType = KeyboardType.Number, + ), + keyboardActions = KeyboardActions(), + ), + ) + } + + override fun convertBack( + normalFee: Fee.Ethereum.Legacy, + value: ImmutableList, + ): Fee.Ethereum.Legacy { + val feeAmount = value[FEE_AMOUNT].value.parseToBigDecimal(value[FEE_AMOUNT].decimals) + val gasPrice = value[GAS_PRICE].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() + val gasLimit = value[GAS_LIMIT].value.parseToBigDecimal(GAS_DECIMALS).toBigInteger() + + return normalFee.copy( + amount = normalFee.amount.copy(value = feeAmount), + gasPrice = gasPrice, + gasLimit = gasLimit, + ) + } + + override fun getGasLimitIndex(feeValue: Fee.Ethereum.Legacy): Int = GAS_LIMIT + + override fun onValueChange( + feeValue: Fee.Ethereum.Legacy, + customValues: ImmutableList, + index: Int, + value: String, + ): ImmutableList { + val mutableCustomValues = customValues.toMutableList() + return mutableCustomValues.apply { + when (index) { + FEE_AMOUNT -> setOnAmountChange(value, index) + GAS_PRICE -> setOnGasPriceChange(value, index) + GAS_LIMIT -> setOnGasLimitChange(value, index) + else -> set(index, this[index].copy(value = value)) + } + }.toImmutableList() + } + + private fun MutableList.setOnAmountChange(value: String, index: Int) { + val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) + if (value.isBlank()) { + setEmpty(FEE_AMOUNT) + setEmpty(GAS_PRICE) + } else { + val newFeeAmountDecimal = value.parseToBigDecimal(this[FEE_AMOUNT].decimals) + val newFeeAmount = newFeeAmountDecimal.movePointRight(this[GAS_PRICE].decimals) // from ETH to GWEI + val newGasPrice = newFeeAmount.divide(gasLimit, this[GAS_PRICE].decimals, RoundingMode.HALF_UP) + set(GAS_PRICE, this[GAS_PRICE].copy(value = newGasPrice.parseBigDecimal(this[GAS_PRICE].decimals))) + set( + index, + this[index].copy( + value = value, + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmountDecimal, + appCurrency = appCurrencyProvider(), + ), + ), + ) + } + } + + private fun MutableList.setOnGasPriceChange(value: String, index: Int) { + val gasLimit = this[GAS_LIMIT].value.parseToBigDecimal(this[GAS_LIMIT].decimals) + if (value.isBlank()) { + setEmpty(FEE_AMOUNT) + setEmpty(GAS_PRICE) + } else { + val newGasPrice = value.parseToBigDecimal(this[GAS_PRICE].decimals) + .movePointLeft(this[GAS_PRICE].decimals) // from GWEI to ETH + val newFeeAmount = gasLimit * newGasPrice + set( + FEE_AMOUNT, + this[FEE_AMOUNT].copy( + value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), + ), + ) + set(index, this[index].copy(value = value)) + } + } + + private fun MutableList.setOnGasLimitChange(value: String, index: Int) { + if (value.isBlank()) { + setEmpty(FEE_AMOUNT) + setEmpty(GAS_LIMIT) + } else { + val newGasLimit = value.parseToBigDecimal(this[GAS_LIMIT].decimals) + val gasPrice = this[GAS_PRICE].value.parseToBigDecimal(this[GAS_PRICE].decimals) + .movePointLeft(this[GAS_PRICE].decimals) // from GWEI to ETH + + val newFeeAmount = newGasLimit * gasPrice + + set( + index = FEE_AMOUNT, + element = this[FEE_AMOUNT].copy( + value = newFeeAmount.parseBigDecimal(this[FEE_AMOUNT].decimals), + label = getFiatReference( + rate = feeCryptoCurrencyStatusProvider()?.value?.fiatRate, + value = newFeeAmount, + appCurrency = appCurrencyProvider(), + ), + ), + ) + + val isNotExceedBalance = checkExceedBalance( + feeBalance = feeCryptoCurrencyStatusProvider()?.value?.amount, + feeAmount = newFeeAmount, + ) + + set( + index = index, + element = this[index].copy( + value = value, + keyboardOptions = KeyboardOptions( + imeAction = if (!isNotExceedBalance) ImeAction.None else ImeAction.Done, + keyboardType = KeyboardType.Number, + ), + ), + ) + } + } + + private companion object { + const val GAS_PRICE = 1 + const val GAS_LIMIT = 2 + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt index 920e6ce4cc..cc612804f3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt @@ -2,6 +2,7 @@ package com.tangem.features.staking.impl.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.action.StakingActionType internal sealed class StakingAnalyticsEvents( @@ -147,14 +148,18 @@ internal sealed class StakingAnalyticsEvents( data object ButtonExplore : StakingAnalyticsEvents(event = "Button - Explore") - data class StakingError( + data class StakekitError( val token: String, - val errorType: String, + val stakeKitError: StakingError, ) : StakingAnalyticsEvents( event = "Errors", params = mapOf( AnalyticsParam.TOKEN_PARAM to token, - AnalyticsParam.ERROR_DESCRIPTION to errorType, + AnalyticsParam.ERROR_TYPE to if (stakeKitError is StakingError.UnknownError) { + stakeKitError.message ?: "Unknown" + } else { + stakeKitError.javaClass.simpleName + }, ), ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index 00bc11c717..b618387124 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -69,9 +69,9 @@ internal class YieldBalancesConverter( val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data val rewards = yieldBalance?.balance?.items - ?.filter { it.type == BalanceType.REWARDS } + ?.filter { it.type == BalanceType.REWARDS && !it.amount.isZero() } - val isActionable = rewards?.all { it.pendingActions.isNotEmpty() } == true + val isActionable = rewards?.any { it.pendingActions.isNotEmpty() } == true val isRewardsClaimable = rewards?.isNotEmpty() == true val isSolana = isSolana(cryptoCurrencyStatus.currency.network.id.value) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index 2daa181826..0b19f03422 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -135,7 +135,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( } if (result.isNullOrEmpty()) { - onStakingFeeError(StakingError.UnknownError) + onStakingFeeError(StakingError.UnknownError()) return } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index 90fc4e9295..2ac221975e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -87,7 +87,7 @@ internal class StakingTransactionSender @AssistedInject constructor( ) if (fullTransactionsData.isNullOrEmpty()) { - onConstructError(StakingError.UnknownError) + onConstructError(StakingError.UnknownError()) return } @@ -202,9 +202,9 @@ internal class StakingTransactionSender @AssistedInject constructor( ), ).getOrElse { analyticsEventHandler.send( - StakingAnalyticsEvents.StakingError( - token = state.cryptoCurrencyName, - errorType = it.javaClass.simpleName, + StakingAnalyticsEvents.StakekitError( + token = state.cryptoCurrencySymbol, + stakeKitError = it, ), ) onConstructError(it) @@ -260,9 +260,9 @@ internal class StakingTransactionSender @AssistedInject constructor( ) .onLeft { error -> analyticsEventHandler.send( - StakingAnalyticsEvents.StakingError( - token = stateController.value.cryptoCurrencyName, - errorType = error.javaClass.simpleName, + StakingAnalyticsEvents.StakekitError( + token = stateController.value.cryptoCurrencySymbol, + stakeKitError = error, ), ) saveUnsubmittedHashUseCase.invoke( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index dae491e1b8..723a0ffd7c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -10,10 +10,8 @@ import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.staking.impl.R -import com.tangem.features.staking.impl.presentation.state.FeeState -import com.tangem.features.staking.impl.presentation.state.StakingStates -import com.tangem.features.staking.impl.presentation.state.StakingUiState -import com.tangem.features.staking.impl.presentation.state.utils.getRewardSchedule +import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.features.staking.impl.presentation.state.utils.getRewardScheduleText import com.tangem.utils.transformer.Transformer internal class SetConfirmationStateLoadingTransformer( @@ -45,9 +43,10 @@ internal class SetConfirmationStateLoadingTransformer( fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, ) - val rewardSchedule = getRewardSchedule( - yield.metadata.rewardSchedule, - cryptoCurrency.network.id.value, + val rewardSchedule = getRewardScheduleText( + rewardSchedule = yield.metadata.rewardSchedule, + networkId = cryptoCurrency.network.id.value, + decapitalize = true, ) return if (isEnterAction && amountDecimal != null && rewardSchedule != null) { resourceReference( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index c3ba140e75..9689522cd7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -20,7 +20,7 @@ import com.tangem.features.staking.impl.presentation.state.* import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import com.tangem.features.staking.impl.presentation.state.converters.RewardsValidatorStateConverter import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter -import com.tangem.features.staking.impl.presentation.state.utils.getRewardSchedule +import com.tangem.features.staking.impl.presentation.state.utils.getRewardScheduleText import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents import com.tangem.lib.crypto.BlockchainUtils.isPolkadot import com.tangem.utils.Provider @@ -192,7 +192,11 @@ internal class SetInitialDataStateTransformer( } private fun createRewardScheduleItem(): RoundedListWithDividersItemData? { - val endTextReference = getRewardScheduleText() ?: return null + val endTextReference = getRewardScheduleText( + rewardSchedule = yield.metadata.rewardSchedule, + networkId = cryptoCurrencyStatusProvider().currency.network.id.value, + decapitalize = false, + ) ?: return null return RoundedListWithDividersItemData( id = R.string.staking_details_reward_schedule, @@ -207,7 +211,9 @@ internal class SetInitialDataStateTransformer( } private fun getAprRange(validators: List): TextReference { - val aprValues = validators.mapNotNull { it.apr } + val aprValues = validators + .filter { it.preferred } + .mapNotNull { it.apr } val minApr = aprValues.min() val maxApr = aprValues.max() @@ -221,18 +227,6 @@ internal class SetInitialDataStateTransformer( return resourceReference(R.string.common_range, wrappedList(formattedMinApr, formattedMaxApr)) } - private fun getRewardScheduleText(): TextReference? { - val rewardSchedule = getRewardSchedule( - yield.metadata.rewardSchedule, - cryptoCurrencyStatusProvider().currency.network.id.value, - ) ?: return null - - return resourceReference( - R.string.staking_reward_schedule_each, - wrappedList(rewardSchedule), - ) - } - private companion object { val EQUALITY_THRESHOLD = BigDecimal(1E-10) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt index ea5f081cfd..8350e74f74 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/approval/ShowApprovalBottomSheetTransformer.kt @@ -51,7 +51,7 @@ internal class ShowApprovalBottomSheetTransformer( data = GiveTxPermissionState.ReadyForRequest( currency = cryptoCurrency.symbol, amount = amountState.amountTextField.value, - approveType = ApproveType.LIMITED, + approveType = ApproveType.UNLIMITED, walletAddress = walletAddress, spenderAddress = validatorAddress, fee = resourceReference( @@ -71,6 +71,7 @@ internal class ShowApprovalBottomSheetTransformer( formatArgs = wrappedList(cryptoCurrency.symbol), ), dialogText = resourceReference(R.string.give_permission_staking_footer), + footerText = resourceReference(R.string.staking_give_permission_fee_footer), onChangeApproveType = prevState.clickIntents::onApproveTypeChange, ), onCancel = onDismiss, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingRewardsUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingRewardsUtils.kt index 5012e225b7..510266948e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingRewardsUtils.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingRewardsUtils.kt @@ -16,23 +16,45 @@ private data object StakingRewardSchedule { val SOLANA_SCHEDULE = 2 to 3 } -internal fun getRewardSchedule(schedule: Yield.Metadata.RewardSchedule, networkId: String): TextReference? = - when (schedule) { +internal fun getRewardScheduleText( + rewardSchedule: Yield.Metadata.RewardSchedule, + networkId: String, + decapitalize: Boolean, +): TextReference? { + return when (rewardSchedule) { + Yield.Metadata.RewardSchedule.WEEK -> resourceReference( + id = R.string.staking_reward_schedule_week, + decapitalize = decapitalize, + ) + Yield.Metadata.RewardSchedule.HOUR -> resourceReference( + id = R.string.staking_reward_schedule_hour, + decapitalize = decapitalize, + ) + Yield.Metadata.RewardSchedule.DAY -> resourceReference( + id = R.string.staking_reward_schedule_day, + decapitalize = decapitalize, + ) + Yield.Metadata.RewardSchedule.MONTH -> resourceReference( + id = R.string.staking_reward_schedule_month, + decapitalize = decapitalize, + ) Yield.Metadata.RewardSchedule.BLOCK, Yield.Metadata.RewardSchedule.EPOCH, Yield.Metadata.RewardSchedule.ERA, - -> getCustomRewardSchedule(networkId) - Yield.Metadata.RewardSchedule.WEEK -> resourceReference(R.string.common_week) - Yield.Metadata.RewardSchedule.HOUR -> resourceReference(R.string.common_hour) - Yield.Metadata.RewardSchedule.DAY -> pluralReference(R.plurals.common_days_no_param, count = 1) - Yield.Metadata.RewardSchedule.MONTH -> resourceReference(R.string.common_month) - Yield.Metadata.RewardSchedule.UNKNOWN -> null + -> getCustomRewardSchedule( + networkId = networkId, + decapitalize = decapitalize, + ) + else -> null } +} -private fun getCustomRewardSchedule(networkId: String): TextReference? { +private fun getCustomRewardSchedule(networkId: String, decapitalize: Boolean = false): TextReference? { return when { isSolana(networkId) -> { combinedReference( + resourceReference(id = R.string.staking_reward_schedule_each_plural, decapitalize = decapitalize), + stringReference(NON_BREAKING_SPACE.toString()), stringReference("${SOLANA_SCHEDULE.first}$MINUS${SOLANA_SCHEDULE.second}$NON_BREAKING_SPACE"), pluralReference( id = R.plurals.common_days_no_param, @@ -42,11 +64,13 @@ private fun getCustomRewardSchedule(networkId: String): TextReference? { } isCosmos(networkId) -> { combinedReference( + resourceReference(id = R.string.staking_reward_schedule_each_plural, decapitalize = decapitalize), + stringReference(NON_BREAKING_SPACE.toString()), stringReference("${COSMOS_SCHEDULE.first}$MINUS${COSMOS_SCHEDULE.second}$NON_BREAKING_SPACE"), resourceReference(R.string.common_second_no_param), ) } - isTron(networkId) -> pluralReference(R.plurals.common_days_no_param, count = 1) + isTron(networkId) -> resourceReference(id = R.string.staking_reward_schedule_day, decapitalize = decapitalize) else -> null } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt index e42c4e7c6c..faaf8c499c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt @@ -55,6 +55,7 @@ internal fun StakingClaimRewardsValidatorContent( .roundedShapeItemDecoration(index, state.rewards.lastIndex, false) .background(TangemTheme.colors.background.action) .clickable( + enabled = item.pendingActions.isNotEmpty(), onClick = { clickIntents.onActiveStake(item) }, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 8e3d7ac5ee..6639fdf7cb 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -244,9 +244,9 @@ internal class StakingViewModel @Inject constructor( }, onStakingFeeError = { error -> analyticsEventHandler.send( - StakingAnalyticsEvents.StakingError( + StakingAnalyticsEvents.StakekitError( value.cryptoCurrencySymbol, - error.javaClass.simpleName, + error, ), ) stateController.update(AddStakingErrorTransformer()) @@ -283,9 +283,9 @@ internal class StakingViewModel @Inject constructor( onConstructError = { error -> Timber.e(error.toString()) analyticsEventHandler.send( - StakingAnalyticsEvents.StakingError( + StakingAnalyticsEvents.StakekitError( token = value.cryptoCurrencySymbol, - errorType = error.javaClass.simpleName, + stakeKitError = error, ), ) stakingEventFactory.createStakingErrorAlert(error) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt index bf614cd4a1..0548a65ea7 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/DefaultSwapRepository.kt @@ -169,14 +169,15 @@ internal class DefaultSwapRepository( return withContext(coroutineDispatcher.io) { either { catch( - { + block = { exchangeStatusConverter.convert( tangemExpressApi .getExchangeStatus(txId) .getOrThrow(), ) }, - { + catch = { + Timber.e("getExchangeStatus error: $it") raise(UnknownError(it.message)) }, ) diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt index d782b606f9..cb3ba18d9f 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/ExchangeStatusConverter.kt @@ -9,7 +9,7 @@ internal class ExchangeStatusConverter : Converter ExchangeStatusState( status = ExchangeStatus.Verifying, @@ -279,6 +289,12 @@ internal class TokenDetailsSwapTransactionsStateConverter( isActive = true, isDone = false, ) + isPaused -> ExchangeStatusState( + status = ExchangeStatus.Paused, + text = resourceReference(id = R.string.express_exchange_status_paused), + isActive = true, + isDone = false, + ) hasFailed || isFailed || isRefunded -> ExchangeStatusState( status = ExchangeStatus.Failed, text = TextReference.Res(R.string.express_exchange_status_failed), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt index ff2dd3baff..ec2a2b3cfb 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt @@ -18,13 +18,17 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.ExchangeStatusState import com.tangem.features.tokendetails.impl.R import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList @Composable internal fun ExchangeStatusBlock( @@ -103,34 +107,34 @@ private fun ExchangeStatusStep( label = "Exchange Step Change Success", modifier = Modifier .size(TangemTheme.dimens.size20), - ) { + ) { state -> when { - it.status == ExchangeStatus.Cancelled -> ExchangeStep( - iconRes = R.drawable.ic_close_24, - color = TangemTheme.colors.icon.warning, - isDone = false, - ) - it.status == ExchangeStatus.Failed -> ExchangeStep( - iconRes = R.drawable.ic_close_24, - color = TangemTheme.colors.icon.warning, - isDone = it.isDone, - ) - it.status == ExchangeStatus.Refunded -> ExchangeStep( - iconRes = R.drawable.ic_close_24, - color = TangemTheme.colors.icon.warning, - isDone = it.isDone, - ) - it.status == ExchangeStatus.Verifying -> ExchangeStep( + state.status == ExchangeStatus.Cancelled -> { + ExchangeStep( + iconRes = R.drawable.ic_close_24, + color = TangemTheme.colors.icon.warning, + isDone = false, + ) + } + state.status == ExchangeStatus.Failed || state.status == ExchangeStatus.Refunded || + state.status == ExchangeStatus.Paused -> { + ExchangeStep( + iconRes = R.drawable.ic_close_24, + color = TangemTheme.colors.icon.warning, + isDone = state.isDone, + ) + } + state.status == ExchangeStatus.Verifying -> ExchangeStep( iconRes = R.drawable.ic_exclamation_24, color = TangemTheme.colors.icon.attention, - isDone = it.isDone, + isDone = state.isDone, ) - it.isDone -> ExchangeStep( + state.isDone -> ExchangeStep( iconRes = R.drawable.ic_check_24, color = TangemTheme.colors.icon.primary1, isDone = true, ) - it.isActive -> ExchangeStepInProgress() + state.isActive -> ExchangeStepInProgress() else -> ExchangeStepDefault() } } @@ -144,11 +148,14 @@ private fun ExchangeStatusStep( @Composable private fun ExchangeStatusStepText(stepStatus: ExchangeStatusState) { + val status = stepStatus.status + val textColor = when { - stepStatus.status == ExchangeStatus.Cancelled -> TangemTheme.colors.icon.warning - stepStatus.status == ExchangeStatus.Refunded -> TangemTheme.colors.icon.warning - stepStatus.status == ExchangeStatus.Failed && !stepStatus.isDone -> TangemTheme.colors.icon.warning - stepStatus.status == ExchangeStatus.Verifying && !stepStatus.isDone -> TangemTheme.colors.icon.attention + status == ExchangeStatus.Cancelled || status == ExchangeStatus.Refunded || status == ExchangeStatus.Paused -> { + TangemTheme.colors.icon.warning + } + status == ExchangeStatus.Failed && !stepStatus.isDone -> TangemTheme.colors.icon.warning + status == ExchangeStatus.Verifying && !stepStatus.isDone -> TangemTheme.colors.icon.attention stepStatus.isDone -> TangemTheme.colors.text.primary1 !stepStatus.isActive -> TangemTheme.colors.text.disabled else -> TangemTheme.colors.text.primary1 @@ -222,4 +229,35 @@ private fun ExchangeStepSeparator() { shape = CircleShape, ), ) +} + +@Preview +@Composable +private fun Preview_ExchangeStatusBlock() { + val base = ExchangeStatusState( + status = ExchangeStatus.Failed, + text = resourceReference(id = R.string.express_exchange_status_failed), + isActive = true, + isDone = false, + ) + + TangemThemePreview { + ExchangeStatusBlock( + statuses = listOf( + base, + base.copy(isActive = false, isDone = false), + base.copy(isActive = true, isDone = false), + base.copy(isActive = true, isDone = true), + ExchangeStatusState( + status = ExchangeStatus.Paused, + text = resourceReference(id = R.string.express_exchange_status_paused), + isActive = true, + isDone = false, + ), + ) + .toImmutableList(), + showLink = false, + onClick = {}, + ) + } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingBalanceBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingBalanceBlock.kt index 1107eba6ae..76bc8386d6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingBalanceBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingBalanceBlock.kt @@ -1,18 +1,12 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.material3.ripple import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -23,7 +17,7 @@ import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakedBlock +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingBalanceBlock import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM import com.tangem.features.tokendetails.impl.R import com.tangem.utils.StringsSigns @@ -36,16 +30,7 @@ internal fun StakingBalanceBlock( ) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = modifier - .fillMaxWidth() - .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.primary) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = ripple(), - onClick = state.onStakeClicked, - ) - .padding(TangemTheme.dimens.spacing12), + modifier = modifier.fillMaxWidth(), ) { Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), @@ -108,6 +93,6 @@ private fun StakingBalanceBlock_Preview( private class StakingBalanceBlockPreviewProvider : PreviewParameterProvider { override val values: Sequence - get() = sequenceOf(stakedBlock) + get() = sequenceOf(stakingBalanceBlock) } // endregion \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingUnavailableBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingUnavailableBlock.kt index 2341b0cab8..2047a1f09f 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingUnavailableBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/StakingUnavailableBlock.kt @@ -1,12 +1,10 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking import android.content.res.Configuration -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.res.TangemTheme @@ -17,11 +15,7 @@ import com.tangem.features.tokendetails.impl.R internal fun StakingTemporaryUnavailableBlock(modifier: Modifier = Modifier) { Column( verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), - modifier = modifier - .fillMaxWidth() - .clip(TangemTheme.shapes.roundedCornersXMedium) - .background(TangemTheme.colors.background.primary) - .padding(TangemTheme.dimens.spacing12), + modifier = modifier.fillMaxWidth(), ) { Text( text = stringResource(R.string.staking_native), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt index a4db00c78a..a8ba9f84c2 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/staking/TokenStakingBlock.kt @@ -3,8 +3,11 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components. import android.content.res.Configuration import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -14,18 +17,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider -import com.tangem.core.ui.components.RectangleShimmer -import com.tangem.core.ui.components.SecondaryButton -import com.tangem.core.ui.components.SpacerW8 -import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.components.* import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.getGreyScaleColorFilter +import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingBalanceBlock import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingAvailableBlock import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingLoadingBlock import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingTemporaryUnavailableBlock -import com.tangem.feature.tokendetails.presentation.tokendetails.state.IconState import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.CurrencyIcon import com.tangem.features.tokendetails.impl.R @@ -39,26 +39,35 @@ import com.tangem.features.tokendetails.impl.R */ @Composable internal fun TokenStakingBlock(state: StakingBlockUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) { - AnimatedContent( - targetState = state, - contentAlignment = Alignment.CenterStart, - label = "Staking block animation", + Column( + modifier = modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.primary) + .clickable( + enabled = state is StakingBlockUM.Staked, + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(), + onClick = { (state as? StakingBlockUM.Staked)?.onStakeClicked?.invoke() }, + ) + .fillMaxWidth() + .padding(all = TangemTheme.dimens.spacing12), ) { - when (it) { - is StakingBlockUM.TemporaryUnavailable -> StakingTemporaryUnavailableBlock(modifier) - is StakingBlockUM.Loading -> StakingLoading( - iconState = it.iconState, - modifier = modifier, - ) - is StakingBlockUM.Staked -> StakingBalanceBlock( - state = it, - isBalanceHidden = isBalanceHidden, - modifier = modifier, - ) - is StakingBlockUM.StakeAvailable -> StakingAvailableContent( - state = it, - modifier = modifier, - ) + AnimatedContent( + targetState = state, + contentAlignment = Alignment.CenterStart, + label = "Staking block animation", + ) { + when (it) { + is StakingBlockUM.TemporaryUnavailable -> StakingTemporaryUnavailableBlock() + is StakingBlockUM.Loading -> StakingLoading() + is StakingBlockUM.Staked -> StakingBalanceBlock( + state = it, + isBalanceHidden = isBalanceHidden, + ) + is StakingBlockUM.StakeAvailable -> StakingAvailableContent( + state = it, + ) + } } } } @@ -66,13 +75,7 @@ internal fun TokenStakingBlock(state: StakingBlockUM, isBalanceHidden: Boolean, @Composable private fun StakingAvailableContent(state: StakingBlockUM.StakeAvailable, modifier: Modifier = Modifier) { Column( - modifier = modifier - .background( - color = TangemTheme.colors.background.primary, - shape = TangemTheme.shapes.roundedCornersXMedium, - ) - .fillMaxWidth() - .padding(all = TangemTheme.dimens.spacing12), + modifier = modifier.fillMaxWidth(), ) { Row { val (alpha, colorFilter) = remember(state.iconState.isGrayscale) { @@ -115,52 +118,30 @@ private fun StakingAvailableContent(state: StakingBlockUM.StakeAvailable, modifi } @Composable -private fun StakingLoading(iconState: IconState, modifier: Modifier = Modifier) { +private fun StakingLoading(modifier: Modifier = Modifier) { Column( - modifier = modifier - .background( - color = TangemTheme.colors.background.primary, - shape = TangemTheme.shapes.roundedCornersXMedium, - ) - .fillMaxWidth() - .heightIn(min = TangemTheme.dimens.size72) - .padding(all = TangemTheme.dimens.spacing12), - + modifier = modifier.fillMaxWidth(), ) { Row { - val (alpha, colorFilter) = remember(iconState.isGrayscale) { - getGreyScaleColorFilter(iconState.isGrayscale) - } - CurrencyIcon( - modifier = Modifier - .size(TangemTheme.dimens.size20) - .clip(TangemTheme.shapes.roundedCorners8) - .align(Alignment.CenterVertically), - icon = iconState, - alpha = alpha, - colorFilter = colorFilter, - ) - SpacerW8() Column { RectangleShimmer( modifier = Modifier .fillMaxWidth() .height(TangemTheme.dimens.size20), ) - Spacer(modifier = Modifier.size(TangemTheme.dimens.size4)) + Spacer(modifier = Modifier.size(TangemTheme.dimens.size8)) RectangleShimmer( modifier = Modifier .fillMaxWidth() - .height(TangemTheme.dimens.size20), + .height(TangemTheme.dimens.size36), ) Spacer(modifier = Modifier.size(TangemTheme.dimens.size8)) } } - SecondaryButton( - modifier = Modifier.fillMaxWidth(), - showProgress = true, - text = TextReference.EMPTY.resolveReference(), - onClick = { /* no-op */ }, + RectangleShimmer( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size48), ) } } @@ -186,6 +167,7 @@ private class StakingBlockStateProvider : CollectionPreviewParameterProvider + items(items = wallets, contentType = { it.id.stringValue }) { state -> WalletCard( state = state, isBalanceHidden = isBalanceHidden, - modifier = Modifier - .animateItemPlacement() - .width(itemWidth), + modifier = Modifier.width(itemWidth), ) } } diff --git a/features/wallet/impl/src/main/res/drawable/ill_bitcoingold_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_bitcoingold_card2_120_106.webp new file mode 100644 index 0000000000..514ee528e9 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_bitcoingold_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_bitcoingold_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_bitcoingold_card3_120_106.webp new file mode 100644 index 0000000000..e24eb358d1 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_bitcoingold_card3_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_card2_with_ring_120_106.png b/features/wallet/impl/src/main/res/drawable/ill_card2_with_ring_120_106.png deleted file mode 100644 index 3f71b06f0e..0000000000 Binary files a/features/wallet/impl/src/main/res/drawable/ill_card2_with_ring_120_106.png and /dev/null differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_card2_with_ring_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_card2_with_ring_120_106.webp new file mode 100644 index 0000000000..c937ff1dfa Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_card2_with_ring_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_card_with_ring_120_106.png b/features/wallet/impl/src/main/res/drawable/ill_card_with_ring_120_106.png deleted file mode 100644 index adee5dfbc2..0000000000 Binary files a/features/wallet/impl/src/main/res/drawable/ill_card_with_ring_120_106.png and /dev/null differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_card_with_ring_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_card_with_ring_120_106.webp new file mode 100644 index 0000000000..b4618c08b8 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_card_with_ring_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_kaspa3_card2_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_kaspa3_card2_120_106.webp new file mode 100644 index 0000000000..1eb50c47e7 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_kaspa3_card2_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_kaspa3_card3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_kaspa3_card3_120_106.webp new file mode 100644 index 0000000000..3cbda287b1 Binary files /dev/null and b/features/wallet/impl/src/main/res/drawable/ill_kaspa3_card3_120_106.webp differ diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index ea8a8f48ac..9bf5436270 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -69,7 +69,6 @@ zxingQrCode = "3.5.1" mviCore = "1.3.1" kotlinSerialization = "1.4.1" arrow = "1.2.3" -reactiveNetwork = "3.0.8" walletConnectCore = "1.18.0" walletConnectWeb3 = "1.11.0" prettyLogger = "2.2.0" @@ -89,7 +88,7 @@ markdownComposeView = "0.5.4" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-821" +tangemBlockchainSdk = "develop-835" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-391" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ @@ -252,7 +251,6 @@ mviCore-watcher = { module = "com.github.badoo.mvicore:mvicore-diff", version.re kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" } arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" } arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" } -reactive-network = { module = "com.github.pwittchen:reactivenetwork-rx2", version.ref = "reactiveNetwork" } walletConnectCore = { module = "com.walletconnect:android-core", version.ref = "walletConnectCore" } walletConnectWeb3 = { module = "com.walletconnect:web3wallet", version.ref = "walletConnectWeb3" } prettyLogger = { module = "com.orhanobut:logger", version.ref = "prettyLogger" } diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index 3132bfa610..736a97fa6c 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -405,4 +405,6 @@ private val excludedBlockchains = listOf( Blockchain.Unknown, Blockchain.Nexa, Blockchain.NexaTestnet, + Blockchain.Casper, + Blockchain.CasperTestnet, ) \ No newline at end of file