Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-21 12:06:39 +03:00
commit 8bbeffdda9
30 changed files with 494 additions and 159 deletions

View file

@ -4,6 +4,7 @@ import android.net.Uri
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.ifNotNull
import com.tangem.common.extensions.toHexString
@ -226,7 +227,7 @@ private fun navigateToWalletScreen() {
}
}
private suspend fun readCard(onSuccess: suspend (ScanResponse) -> Unit) {
private suspend fun readCard(onSuccess: suspend (ScanResponse) -> Unit, onFailure: (TangemError) -> Unit) {
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
@ -247,6 +248,7 @@ private suspend fun readCard(onSuccess: suspend (ScanResponse) -> Unit) {
Timber.e(it, "Unable to scan card")
delay(HIDE_PROGRESS_DELAY)
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
onFailure(it)
},
onSuccess = onSuccess,
)
@ -605,9 +607,14 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
}
if (scanResponse == null) {
scope.launch {
readCard { newScanResponse ->
handleFinishBackup(newScanResponse)
}
readCard(
onSuccess = { newScanResponse ->
handleFinishBackup(newScanResponse)
},
onFailure = {
store.dispatchNavigationAction(AppRouter::pop)
},
)
}
} else {
handleFinishBackup(scanResponse)
@ -643,10 +650,15 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
} else {
delay(HIDE_PROGRESS_DELAY)
readCard { newScanResponse ->
scanResponse = newScanResponse
userWallet = createUserWallet(newScanResponse, backupState)
}
readCard(
onSuccess = { newScanResponse ->
scanResponse = newScanResponse
userWallet = createUserWallet(newScanResponse, backupState)
},
onFailure = {
store.dispatchNavigationAction(AppRouter::pop)
},
)
}
val notActivatedCardIds = gatherCardIds(backupState, card).mapNotNull {

View file

@ -180,6 +180,7 @@
<string name="common_terms_of_use">Nutzungsbedingungen</string>
<string name="common_today">Heute</string>
<string name="common_transaction_failed">Transaktion fehlgeschlagen</string>
<string name="common_transaction_status">Transaktionsstatus</string>
<string name="common_transactions">Transaktionen</string>
<string name="common_transfer">Überweisung</string>
<string name="common_understand">Ich verstehe</string>
@ -187,6 +188,7 @@
<string name="common_unreachable">Nicht erreichbar</string>
<string name="common_unstake">staking beenden</string>
<string name="common_week">woche</string>
<string name="common_with">mit</string>
<string name="common_yes">Ja</string>
<string name="contract_address_copied_message">Vertragsadresse kopiert!</string>
<string name="currency_subtitle_expanded">Verfügbare Netzwerke</string>
@ -292,6 +294,9 @@
<string name="express_provider_not_available">Für dieses Paar nicht verfügbar</string>
<string name="express_provider_permission_needed">Erlaubnis erforderlich</string>
<string name="express_provider_recommended">Empfohlen</string>
<string name="express_status_bought">Gekauft %s</string>
<string name="express_status_buying">Kauf %s</string>
<string name="express_status_buying_active">Kauf %s...</string>
<string name="express_token_list_empty_search">Keine Token gefunden. Bitte versuche eine andere Anfrage</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">Transaktions-ID kopiert</string>
@ -568,13 +573,14 @@
<string name="onramp_no_available_providers">Keine verfügbaren Anbieter für diese Währung</string>
<string name="onramp_pay_with">Bezahlen mit</string>
<string name="onramp_redirecting_to_provider_subtitle">Du kannst Deine Transaktion beim Drittanbieter %s abschließen.</string>
<string name="onramp_redirecting_to_provider_title">Umleitung auf %s </string>
<string name="onramp_redirecting_to_provider_title">Umleitung auf %s…</string>
<string name="onramp_residency_bottomsheet_country_not_supported">Unsere Dienstleistungen sind in diesem Land nicht verfügbar</string>
<string name="onramp_residency_bottomsheet_country_subtitle">Änder oder bestätige bitte</string>
<string name="onramp_residency_bottomsheet_title">Dein Wohnsitz wurde identifiziert als</string>
<string name="onramp_settings_residence">Residenz</string>
<string name="onramp_settings_residence_description">Bitte wähle das richtige Land aus, um korrekte Zahlungsoptionen und Dienstleistungen zu gewährleisten.</string>
<string name="onramp_settings_title">Einstellungen</string>
<string name="onramp_transaction_status_footer_text">Du kannst den Transaktionsstatus auf der Token-Seite überprüfen.</string>
<string name="onramp_via">Über</string>
<string name="organize_tokens_group">Gruppe erstellen</string>
<string name="organize_tokens_sort_by_balance">Nach Guthaben</string>
@ -853,14 +859,14 @@
<string name="toast_balances_shown">Angezeigte Salden</string>
<string name="toast_undo">Rückgängig machen</string>
<string name="token_button_unavailability_generic_description">Dieser Vorgang ist derzeit nicht verfügbar. Bitte versuche es später noch einmal.</string>
<string name="token_button_unavailability_reason_buy_unavailable">Der Kauf von %s ist im Moment nicht verfügbar. Bitte prüfe später ob es Updates gibt.</string>
<string name="token_button_unavailability_reason_buy_unavailable">Der Kauf von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen.</string>
<string name="token_button_unavailability_reason_empty_balance_sell">Du hast kein Guthaben um dies zu Verkaufen. Lade dein Konto auf, um Geld von dort zu verkaufen.</string>
<string name="token_button_unavailability_reason_empty_balance_send">Du hast kein Guthaben zum Versenden. Lade dein Konto auf, um Geld von dort aus senden zu können.</string>
<string name="token_button_unavailability_reason_not_exchangeable">Swappen %s ist im Moment nicht verfügbar. Bitte prüfe später ob es Updates gibt.</string>
<string name="token_button_unavailability_reason_not_exchangeable">Das Tauschen von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen.</string>
<string name="token_button_unavailability_reason_pending_transaction_sell">Das Geld für den Verkauf steht zur Verfügung, sobald die ausstehende(n) Transaktion(en) im Netzwerk %s abgeschlossen ist/sind.</string>
<string name="token_button_unavailability_reason_pending_transaction_send">Die Geldsendung wird verfügbar, sobald die ausstehende(n) Transaktion(en) im Netzwerk %s abgeschlossen ist/sind.</string>
<string name="token_button_unavailability_reason_sell_unavailable">Der Verkauf von %s ist im Moment nicht verfügbar. Bitte prüfe später ob es Updates gibt.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Staking %s ist aktuell nicht verfügbar. Prüfe bitte ob es ein neues Update gibt.</string>
<string name="token_button_unavailability_reason_sell_unavailable">Der Verkauf von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Staking %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen.</string>
<string name="token_details_generate_xpub">Generiere XPUB</string>
<string name="token_details_hide_alert_hide">Ausblenden</string>
<string name="token_details_hide_alert_message">Du bist dabei, dieses Token vom Hauptbildschirm auszublenden. Du kannst es jederzeit über die Seite „Token verwalten“ wieder hinzufügen.</string>
@ -953,6 +959,9 @@
<string name="wallet_network_group_title">%s Netzwerk</string>
<string name="wallet_notification_address_copied">Die Adresse wurde erfolgreich kopiert</string>
<string name="wallet_notification_no_internet">Keine Internetverbindung</string>
<string name="wallet_promo_banner_button_title">Jetzt mit 10 %% Rabatt kaufen</string>
<string name="wallet_promo_banner_description">Greife auf 13.000+ Kryptowährungen zu. Kaufe, verkaufe, tausche und stake Sie mit einem einzigen Fingertipp.\nVerbinde bis zu drei Karten für ein Backup.</string>
<string name="wallet_promo_banner_title">Entdecke die Tangem Wallet</string>
<string name="wallet_settings_title">Wallet-Einstellungen</string>
<string name="wallet_title">Tangem</string>
<string name="warning_access_denied_message">Verwende %s oder scanne eine Karte oder Ring, um den Zugriff auf deine Wallet freizuschalten.</string>
@ -998,6 +1007,9 @@
<string name="warning_hedera_missing_token_association_message_brief">Dieses Token muss mit deinem Hedera-Konto verknüpft sein, bevor du ihn erhalten kannst.</string>
<string name="warning_hedera_missing_token_association_title">Verknüpfe deinen Token</string>
<string name="warning_hedera_token_association_not_enough_hbar_message">Nicht genug %s. Lade dein Hedera-Konto auf, um dieses Token zuzuordnen</string>
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Bist Du sicher, dass Du die Transaktion stornieren willst? Du kannst die Transaktion nicht erneut starten.</string>
<string name="warning_kaspa_unfinished_token_transaction_message">Deine Transaktion mit einem Betrag von %1$s %2$s wurde nicht abgeschlossen. Du kannst versuchen, diese erneut abzuschließen.</string>
<string name="warning_kaspa_unfinished_token_transaction_title">Du hast eine nicht abgeschlossene Transaktion</string>
<string name="warning_low_signatures_message">Auf dieser Karte oder Ring sind nur noch %s Unterschriften übrig. Du musst dein gesamtes Guthaben abheben.</string>
<string name="warning_low_signatures_title">Geringe Anzahl von Unterschriften</string>
<string name="warning_manage_tokens_legacy_derivation_message">Token in verschiedenen Netzwerken können unterschiedliche Adressen haben. Überprüfe bei der Überweisung noch einmal, ob deine Adresse mit der des Netzwerks übereinstimmt.</string>

View file

@ -858,14 +858,14 @@
<string name="toast_balances_shown">Saldos mostrados</string>
<string name="toast_undo">Cancelar</string>
<string name="token_button_unavailability_generic_description">Esta operación no está disponible actualmente. Por favor, inténtalo de nuevo más tarde.</string>
<string name="token_button_unavailability_reason_buy_unavailable">Comprar %s no está disponible en este momento. Por favor revise sus actualizaciones.</string>
<string name="token_button_unavailability_reason_buy_unavailable">Comprar %s no está disponible con los proveedores actuales pero estamos trabajando para agregar más opciones.</string>
<string name="token_button_unavailability_reason_empty_balance_sell">No tiene fondos para vender. Recargue su cuenta para poder vender fondos desde ella.</string>
<string name="token_button_unavailability_reason_empty_balance_send">No tienes fondos para enviar. Recarga tu cuenta para poder enviar fondos desde ella.</string>
<string name="token_button_unavailability_reason_not_exchangeable">El servicio de intercambio %s no está disponible en este momento. Por favor consulte nuestras actualizaciones.</string>
<string name="token_button_unavailability_reason_not_exchangeable">El servicio de intercambio %s no está compatible con los proveedores actuales pero estamos trabajando para agregar más opciones.</string>
<string name="token_button_unavailability_reason_pending_transaction_sell">La venta de fondos estará disponible una vez que la(s) transacción(es) pendiente(s) en la red %s se complete</string>
<string name="token_button_unavailability_reason_pending_transaction_send">El envío de fondos estará disponible una vez que se completen las transacciones pendientes en la red %s.</string>
<string name="token_button_unavailability_reason_sell_unavailable">La venta de %s no está disponible en este momento. Por favor consulte nuestras actualizaciones.</string>
<string name="token_button_unavailability_reason_staking_unavailable">El staking %s no está disponible en este momento. Por favor consulte nuestras actualizaciones.</string>
<string name="token_button_unavailability_reason_sell_unavailable">Vender %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones.</string>
<string name="token_button_unavailability_reason_staking_unavailable">El staking de %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones.</string>
<string name="token_details_generate_xpub">Generar XPUB</string>
<string name="token_details_hide_alert_hide">Ocultar</string>
<string name="token_details_hide_alert_message">Está a punto de ocultar este token de la pantalla principal. Puede volver a agregarlo en cualquier momento a través de la página de gestión de tokens.</string>

View file

@ -850,14 +850,14 @@
<string name="toast_balances_shown">Soldes affichés</string>
<string name="toast_undo">Annuler</string>
<string name="token_button_unavailability_generic_description">Cette opération est actuellement indisponible. Veuillez réessayer plus tard.</string>
<string name="token_button_unavailability_reason_buy_unavailable">L\'achat de %s n\'est pas disponible pour le moment. Veuillez vérifier vos mises à jour.</string>
<string name="token_button_unavailability_reason_buy_unavailable">L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter davantage d\'options.</string>
<string name="token_button_unavailability_reason_empty_balance_sell">Vous n\'avez pas de fonds à vendre. Renflouez votre compte pour pouvoir vendre des fonds à partir de celui-ci.</string>
<string name="token_button_unavailability_reason_empty_balance_send">Vous n\'avez pas de fonds à envoyer. Renflouez votre compte pour pouvoir envoyer des fonds à partir de celui-ci.</string>
<string name="token_button_unavailability_reason_not_exchangeable">Le service d\'échange %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour.</string>
<string name="token_button_unavailability_reason_not_exchangeable">Le service d\'échange %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options.</string>
<string name="token_button_unavailability_reason_pending_transaction_sell">La vente de fonds sera disponible une fois que la ou les transactions en attente dans le réseau %s seront terminées</string>
<string name="token_button_unavailability_reason_pending_transaction_send">L\'envoi de fonds sera disponible une fois la ou les transactions en attente dans le réseau %s terminées.</string>
<string name="token_button_unavailability_reason_sell_unavailable">La vente de %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Le staking %s nest pas disponible pour le moment. Veuillez consulter nos mises à jour.</string>
<string name="token_button_unavailability_reason_sell_unavailable">L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Le staking de %s n\'est pas pris en charge par les fournisseurs actuels, mais nous travaillons à ajouter plus d\'options.</string>
<string name="token_details_generate_xpub">Générer XPUB</string>
<string name="token_details_hide_alert_hide">Masquer</string>
<string name="token_details_hide_alert_message">Vous êtes sur le point de masquer ce jeton de l\'écran principal. Vous pouvez le rajouter à tout moment via la page de gestion des jetons.</string>

View file

@ -291,6 +291,9 @@
<string name="express_provider_not_available">このペアは利用できません</string>
<string name="express_provider_permission_needed">許可が必要です</string>
<string name="express_provider_recommended">推奨</string>
<string name="express_status_bought">%sを買い付けました</string>
<string name="express_status_buying">%sを買い付けています</string>
<string name="express_status_buying_active">%sを買い付けています...</string>
<string name="express_token_list_empty_search">トークンが見つかりません。別のリクエストをお試しください。</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">取引IDをコピーしました</string>
@ -830,7 +833,7 @@
<string name="swapping_alert_cex_description_with_slippage">金額には以下が含まれます: \n • サービス プロバイダーの手数料\n • 取引所からユーザーのアドレスに%1$sを送金するためのネットワーク手数料。 \n\nプロバイダーのスリッページは最大%2$sです</string>
<string name="swapping_alert_dex_description">この金額には、サービスプロバイダーの手数料が含まれています。</string>
<string name="swapping_alert_dex_description_with_slippage">金額にはサービスプロバイダーの手数料が含まれます。 \n\nプロバイダーのスリッページは最大%s です</string>
<string name="swapping_alert_title">手数料</string>
<string name="swapping_alert_title">情報</string>
<string name="swapping_approve_information_text">すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金あなたが支払うを受け取ります。承認後、トークンを交換することができます。</string>
<string name="swapping_approve_information_title">承認</string>
<string name="swapping_fee_estimation_error_text">手数料見積りエラーです。サポートにフィードバックをお送りください。</string>
@ -846,14 +849,14 @@
<string name="toast_balances_shown">残高表示</string>
<string name="toast_undo">元に戻す</string>
<string name="token_button_unavailability_generic_description">この操作は現在利用できません。しばらくしてからもう一度お試しください。</string>
<string name="token_button_unavailability_reason_buy_unavailable">現在、 %sの買付はご利用いただけません。アップデート情報をご確認ください</string>
<string name="token_button_unavailability_reason_buy_unavailable">%sの買付は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます</string>
<string name="token_button_unavailability_reason_empty_balance_sell">売却できる資金がありません。アカウントに入金して、売却できるようにしてください。</string>
<string name="token_button_unavailability_reason_empty_balance_send">送金する資金がありません。アカウントに入金して、送金できるようにしてください。</string>
<string name="token_button_unavailability_reason_not_exchangeable">現在、 %sの交換はご利用いただけません。アップデート情報を確認してください</string>
<string name="token_button_unavailability_reason_not_exchangeable">%sのスワップは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます</string>
<string name="token_button_unavailability_reason_pending_transaction_sell">ネットワーク%s内の保留中の取引が完了すると、資金の売却が可能になります。</string>
<string name="token_button_unavailability_reason_pending_transaction_send">ネットワーク%s内の保留中の取引が完了すると、送金が可能になります。</string>
<string name="token_button_unavailability_reason_sell_unavailable">現在、 %sの売却はご利用いただけません。アップデート情報をご確認ください</string>
<string name="token_button_unavailability_reason_staking_unavailable">%s のステーキングは現在ご利用いただけません。最新情報をご確認ください</string>
<string name="token_button_unavailability_reason_sell_unavailable">%sの売却は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます</string>
<string name="token_button_unavailability_reason_staking_unavailable">%sのステーキングは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます</string>
<string name="token_details_generate_xpub">XPUBを生成する</string>
<string name="token_details_hide_alert_hide">非表示</string>
<string name="token_details_hide_alert_message">このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。</string>
@ -946,6 +949,7 @@
<string name="wallet_network_group_title">%sネットワーク</string>
<string name="wallet_notification_address_copied">アドレスがクリップボードにコピーされました</string>
<string name="wallet_notification_no_internet">インターネット接続がありません</string>
<string name="wallet_promo_banner_button_title">今すぐ10 %%オフで購入</string>
<string name="wallet_promo_banner_description">1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。</string>
<string name="wallet_promo_banner_title">Tangemウォレットを見る</string>
<string name="wallet_settings_title">ウォレット設定</string>
@ -993,6 +997,9 @@
<string name="warning_hedera_missing_token_association_message_brief">このトークンを受け取るには、Hederaアカウントに関連付ける必要があります。</string>
<string name="warning_hedera_missing_token_association_title">トークンを関連付ける</string>
<string name="warning_hedera_token_association_not_enough_hbar_message">%sが不足しています。このトークンを関連付けるには、Hederaアカウントに資金を追加してください。</string>
<string name="warning_kaspa_unfinished_token_transaction_discard_message">取引をキャンセルしてもよろしいですか? 取引を再試行することはできません</string>
<string name="warning_kaspa_unfinished_token_transaction_message">%1$s %2$sの取引が完了しませんでした。もう一度試して完了してください。</string>
<string name="warning_kaspa_unfinished_token_transaction_title">未完了の取引があります</string>
<string name="warning_low_signatures_message">このカードには%sの署名のみが残っています。資金をすべて引き出す必要があります。</string>
<string name="warning_low_signatures_title">署名数が少ないです</string>
<string name="warning_manage_tokens_legacy_derivation_message">異なるネットワーク上のトークンは、異なるアドレスを持つ場合があります。資金を送金する際には、アドレスがネットワークと一致していることを再確認してください。</string>
@ -1018,7 +1025,7 @@
<string name="warning_solana_fee_message">Solanaネットワークが混雑しています。2分以内に取引が完了しない場合は、再度取引を繰り返してください。</string>
<string name="warning_solana_fee_title">Solanaネットワークアラート</string>
<string name="warning_solana_rent_fee_message">Solana ネットワークは 2 日ごとに%1$sのレンタル料を請求します。レンタル料を支払えないアカウントはネットワークから削除されます。アカウントに%2$s以上入金すると、無料で使用できます。</string>
<string name="warning_some_networks_unreachable_message">現在、一部のネットワークにアクセスできません。しばらくしてからもう一度お試しください。</string>
<string name="warning_some_networks_unreachable_message">下にスワイプして更新するか、後でもう一度お試しください。</string>
<string name="warning_some_networks_unreachable_title">一部のネットワークにアクセスできません</string>
<string name="warning_testnet_card_message">これはテストネットカードです。取引処理はできませんので、テストおよび開発目的でのみご利用ください。</string>
<string name="warning_testnet_card_title">テスト目的のみ</string>

View file

@ -183,6 +183,7 @@
<string name="common_terms_of_use">Условиями использования</string>
<string name="common_today">Сегодня</string>
<string name="common_transaction_failed">Ошибка транзакции</string>
<string name="common_transaction_status">Статус транзакции</string>
<string name="common_transactions">Транзакции</string>
<string name="common_transfer">Перевод</string>
<string name="common_understand">Я понял</string>
@ -580,7 +581,7 @@
<string name="onramp_no_available_providers">Для данной валюты нет доступных провайдеров</string>
<string name="onramp_pay_with">Оплата с</string>
<string name="onramp_redirecting_to_provider_subtitle">Вы сможете завершить транзакцию через сервис стороннего провайдера, %s</string>
<string name="onramp_redirecting_to_provider_title">Переход к %s ...</string>
<string name="onramp_redirecting_to_provider_title">Переход к %s...</string>
<string name="onramp_residency_bottomsheet_country_not_supported">Наши сервисы недоступны в данной стране</string>
<string name="onramp_settings_title">Настройки</string>
<string name="onramp_via">Через</string>
@ -965,7 +966,7 @@
<string name="wallet_notification_no_internet">Нет соединения с интернетом</string>
<string name="wallet_promo_banner_button_title">Получить с 10%% скидкой</string>
<string name="wallet_promo_banner_description">Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменяйте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. </string>
<string name="wallet_promo_banner_title">Откройте Tangem Wallet </string>
<string name="wallet_promo_banner_title">Откройте Tangem Wallet</string>
<string name="wallet_settings_title">Настройки кошелька</string>
<string name="wallet_title">Tangem</string>
<string name="warning_access_denied_message">Используйте %s или отсканируйте карту/кольцо, чтобы разблокировать доступ к вашему кошельку</string>
@ -1011,6 +1012,9 @@
<string name="warning_hedera_missing_token_association_message_brief">Этот токен должен быть ассоциирован с вашей учетной записью Hedera, прежде чем вы сможете его принять</string>
<string name="warning_hedera_missing_token_association_title">Ассоциируете свой токен</string>
<string name="warning_hedera_token_association_not_enough_hbar_message">Недостаточно %s. Пополните ваш аккаунт Hedera для ассоциации этого токена</string>
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Вы уверены что хотите отменить эту транзакцию? Вы не сможете отправить ее еще раз</string>
<string name="warning_kaspa_unfinished_token_transaction_message">Ваша транзакция %1$s %2$s не была завершена. Вы можете попробовать отправить ее еще раз.</string>
<string name="warning_kaspa_unfinished_token_transaction_title">У вас есть незавершенная транзакция</string>
<string name="warning_low_signatures_message">На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства.</string>
<string name="warning_low_signatures_title">Малое количество подписей</string>
<string name="warning_manage_tokens_legacy_derivation_message">Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети.</string>

View file

@ -300,6 +300,9 @@
<string name="express_provider_not_available">Недоступно для цієї пари</string>
<string name="express_provider_permission_needed">Потрібен дозвіл</string>
<string name="express_provider_recommended">Рекомендовано</string>
<string name="express_status_bought">Купив %s</string>
<string name="express_status_buying">Купівля %s...</string>
<string name="express_status_buying_active">Купівля %s...</string>
<string name="express_token_list_empty_search">Токенів не знайдено. Будь ласка, спробуйте інший запит</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">ID транзакції скопійовано</string>
@ -975,6 +978,9 @@
<string name="wallet_network_group_title">Мережа %s</string>
<string name="wallet_notification_address_copied">Адреса скопійована в буфер обміну</string>
<string name="wallet_notification_no_internet">Немає підключення до інтернету</string>
<string name="wallet_promo_banner_button_title">Отримати з 10%% знижкою</string>
<string name="wallet_promo_banner_description">Доступ до 13,000+ криптовалют. Купуйте, продавайте, обмінюйте та стейкайте одним дотиком.\nЗєднайте до трьох карток для бекапу.</string>
<string name="wallet_promo_banner_title">Відкрийте Tangem Wallet</string>
<string name="wallet_settings_title">Налаштування гаманця</string>
<string name="wallet_title">Tangem</string>
<string name="warning_access_denied_message">Використовуйте %s або відскануйте картку/кільце, щоб розблокувати доступ до вашого гаманця</string>
@ -1020,6 +1026,9 @@
<string name="warning_hedera_missing_token_association_message_brief">Цей токен повинен бути асоційований з вашим обліковим записом Hedera, перш ніж ви зможете його прийняти</string>
<string name="warning_hedera_missing_token_association_title">Асоціюйте свій токен</string>
<string name="warning_hedera_token_association_not_enough_hbar_message">Недостатньо %s. Поповніть ваш обліковий запис Hedera для асоціації цього токена</string>
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Ви впевнені, що хочете скасувати цю транзакцію? Ви не зможете відправити її ще раз</string>
<string name="warning_kaspa_unfinished_token_transaction_message">Ваша транзакція %1$s %2$s не була завершена. Ви можете спробувати відправити її ще раз.</string>
<string name="warning_kaspa_unfinished_token_transaction_title">У вас є незавершена транзакція</string>
<string name="warning_low_signatures_message">На цій картці залишається лише %s підписів. Ви повинні вивести всі свої кошти.</string>
<string name="warning_low_signatures_title">Низька кількість підписів</string>
<string name="warning_manage_tokens_legacy_derivation_message">Токени в різних мережах можуть мати різні адреси. Переконайтеся, що ваша адреса відповідає мережі, коли переказуєте кошти.</string>

View file

@ -294,6 +294,9 @@
<string name="express_provider_not_available">Unavailable for this pair</string>
<string name="express_provider_permission_needed">Permission Required</string>
<string name="express_provider_recommended">Recommended</string>
<string name="express_status_bought">Bought %s</string>
<string name="express_status_buying">Buying %s</string>
<string name="express_status_buying_active">Buying %s...</string>
<string name="express_token_list_empty_search">No tokens found. Please try another request</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">Transaction ID copied</string>
@ -1004,6 +1007,9 @@
<string name="warning_hedera_missing_token_association_message_brief">This token must be associated with your Hedera account before you can receive it</string>
<string name="warning_hedera_missing_token_association_title">Associate your token</string>
<string name="warning_hedera_token_association_not_enough_hbar_message">Not enough %s. Top up your Hedera account to associate this token</string>
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Are you sure you want to cancel the transaction? You will not be able to retry the transaction again</string>
<string name="warning_kaspa_unfinished_token_transaction_message">Your transaction with an amount of %1$s %2$s was not completed. You can try again to complete it.</string>
<string name="warning_kaspa_unfinished_token_transaction_title">You have unfinished transaction</string>
<string name="warning_low_signatures_message">Only %s signatures are left on this card. You must withdraw all of your funds.</string>
<string name="warning_low_signatures_title">Low signature count</string>
<string name="warning_manage_tokens_legacy_derivation_message">Tokens on different networks can have different addresses. Double-check that your address matches the network when you transfer funds.</string>

View file

@ -0,0 +1,111 @@
package com.tangem.core.ui.components.notifications
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.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.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonColors
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemColorPalette.Dark6
import com.tangem.core.ui.res.TangemColorPalette.Light2
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
@Composable
fun NoteMigrationNotification(config: NotificationConfig, modifier: Modifier = Modifier) {
val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig
button ?: return
Column(
modifier = modifier
.fillMaxWidth()
.clip(shape = TangemTheme.shapes.roundedCornersXMedium)
.background(TangemColorPalette.Dark6),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
modifier = Modifier.heightIn(max = 146.dp),
painter = painterResource(id = R.drawable.banner_note_migration),
contentDescription = null,
contentScale = ContentScale.Fit,
)
Column(
modifier = Modifier.padding(
start = 12.dp,
end = 12.dp,
bottom = 12.dp,
),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
config.title?.let {
Text(
text = it.resolveReference(),
color = TangemColorPalette.White,
style = TangemTheme.typography.h3,
)
}
Text(
text = config.subtitle.resolveReference(),
textAlign = TextAlign.Center,
color = TangemColorPalette.Light5,
style = TangemTheme.typography.body2,
)
TangemButton(
modifier = Modifier.fillMaxWidth(),
text = button.text.resolveReference(),
icon = TangemButtonIconPosition.None,
onClick = button.onClick,
colors = TangemButtonColors(
backgroundColor = Light2,
contentColor = Dark6,
disabledBackgroundColor = TangemTheme.colors.button.disabled,
disabledContentColor = TangemTheme.colors.text.disabled,
),
enabled = true,
textStyle = TangemTheme.typography.subtitle1,
showProgress = false,
)
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 328)
@Composable
private fun NoteMigrationNotification_Preview() {
TangemThemePreview {
NoteMigrationNotification(
NotificationConfig(
title = stringReference("Discover Tangem Wallet"),
subtitle = stringReference(
"Access 13,000+ cryptocurrencies. Buy, sell, swap, and stake with a single tap. Link up to " +
"three cards for a backup.",
),
iconResId = R.drawable.ic_empty_64,
onCloseClick = { },
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = stringReference("Get now"),
onClick = {},
),
),
)
}
}
// endregion

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

View file

@ -64,14 +64,10 @@ internal class DefaultMarketsTokenRepository(
// we shouldn't infinitely retry on the first batch request
val res = if (isFirstBatchFetching) {
catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) {
requestCall()
}
catchListErrorAndSendEvent { requestCall() }
} else {
retryOnError(priority = true) {
catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) {
requestCall()
}
catchListErrorAndSendEvent { requestCall() }
}
}
@ -100,7 +96,7 @@ internal class DefaultMarketsTokenRepository(
marketsApi = marketsApi,
analyticsEventHandler = analyticsEventHandler,
onApiError = {
analyticsEventHandler.send(MarketsDataAnalyticsEvent.List.Error.toEvent())
analyticsEventHandler.send(createListErrorEvent(it).toEvent())
},
)
@ -128,11 +124,9 @@ internal class DefaultMarketsTokenRepository(
interval = interval.toRequestParam(),
)
val result = catchApiErrorAndSendEvent(
errorEvent = MarketsDataAnalyticsEvent.Details.Error(
request = MarketsDataAnalyticsEvent.Details.Error.Request.Chart,
tokenSymbol = tokenSymbol,
),
val result = catchDetailsErrorAndSendEvent(
request = MarketsDataAnalyticsEvent.Details.Error.Request.Chart,
tokenSymbol = tokenSymbol,
) {
response.getOrThrow()
}
@ -146,6 +140,7 @@ internal class DefaultMarketsTokenRepository(
analyticsEventHandler.send(
MarketsDataAnalyticsEvent.ChartNullValuesError(
requestPath = "coins/history",
errorType = MarketsDataAnalyticsEvent.Type.Custom,
),
)
},
@ -159,18 +154,16 @@ internal class DefaultMarketsTokenRepository(
tokenSymbol: String,
) = withContext(dispatcherProvider.io) {
val mappedTokenId = getTokenIdIfL2Network(tokenId)
val response = marketsApi.getCoinsListCharts(
coinIds = mappedTokenId,
currency = fiatCurrencyCode,
interval = interval.toRequestParam(),
)
val chart = catchApiErrorAndSendEvent(errorEvent = MarketsDataAnalyticsEvent.List.Error) {
response.getOrThrow()[mappedTokenId] ?: error(
val chart = catchListErrorAndSendEvent {
marketsApi.getCoinsListCharts(
coinIds = mappedTokenId,
currency = fiatCurrencyCode,
interval = interval.toRequestParam(),
).getOrThrow()[mappedTokenId] ?: error(
"No chart preview data for the token $mappedTokenId",
)
}
return@withContext TokenChartConverter.convert(
interval = interval,
value = chart,
@ -180,6 +173,7 @@ internal class DefaultMarketsTokenRepository(
analyticsEventHandler.send(
MarketsDataAnalyticsEvent.ChartNullValuesError(
requestPath = "coins/history_preview",
errorType = MarketsDataAnalyticsEvent.Type.Custom,
),
)
},
@ -192,19 +186,15 @@ internal class DefaultMarketsTokenRepository(
tokenSymbol: String,
languageCode: String,
) = withContext(dispatcherProvider.io) {
val response = marketsApi.getCoinMarketData(
currency = fiatCurrencyCode,
coinId = tokenId,
language = languageCode,
)
val result = catchApiErrorAndSendEvent(
errorEvent = MarketsDataAnalyticsEvent.Details.Error(
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
tokenSymbol = tokenSymbol,
),
val result = catchDetailsErrorAndSendEvent(
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
tokenSymbol = tokenSymbol,
) {
response.getOrThrow()
marketsApi.getCoinMarketData(
currency = fiatCurrencyCode,
coinId = tokenId,
language = languageCode,
).getOrThrow()
}
val resultResponse = result.applyL2Compatibility(tokenId)
@ -214,19 +204,16 @@ internal class DefaultMarketsTokenRepository(
override suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String, tokenSymbol: String) =
withContext(dispatcherProvider.io) {
// for second markets iteration we should use extended api method with all required fields
val response = tangemTechApi.getQuotes(
currencyId = fiatCurrencyCode,
coinIds = tokenId,
fields = marketsQuoteFields.joinToString(separator = ","),
)
val result = catchApiErrorAndSendEvent(
errorEvent = MarketsDataAnalyticsEvent.Details.Error(
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
tokenSymbol = tokenSymbol,
),
val result = catchDetailsErrorAndSendEvent(
request = MarketsDataAnalyticsEvent.Details.Error.Request.Info,
tokenSymbol = tokenSymbol,
) {
response.getOrThrow()
tangemTechApi.getQuotes(
currencyId = fiatCurrencyCode,
coinIds = tokenId,
fields = marketsQuoteFields.joinToString(separator = ","),
).getOrThrow()
}
return@withContext TokenQuotesShortConverter.convert(tokenId, result).toFull()
@ -272,15 +259,75 @@ internal class DefaultMarketsTokenRepository(
}
}
private inline fun <T> catchApiErrorAndSendEvent(errorEvent: MarketsDataAnalyticsEvent, block: () -> T): T {
inline fun <T> catchListErrorAndSendEvent(block: () -> T): T {
return catchErrorAndSendEvent(block, ::createListErrorEvent)
}
private inline fun <T> catchDetailsErrorAndSendEvent(
request: MarketsDataAnalyticsEvent.Details.Error.Request,
tokenSymbol: String,
block: () -> T,
): T {
return catchErrorAndSendEvent(block) { error ->
createDetailsErrorEvent(error, request, tokenSymbol)
}
}
private inline fun <T> catchErrorAndSendEvent(
block: () -> T,
createErrorEvent: (ApiResponseError) -> MarketsDataAnalyticsEvent,
): T {
return try {
block()
} catch (e: ApiResponseError.HttpException) {
analyticsEventHandler.send(errorEvent.toEvent())
throw e
} catch (e: ApiResponseError.TimeoutException) {
} catch (e: ApiResponseError) {
val errorEvent = createErrorEvent(e)
analyticsEventHandler.send(errorEvent.toEvent())
throw e
}
}
private fun createListErrorEvent(error: ApiResponseError): MarketsDataAnalyticsEvent.List.Error {
return createErrorEvent(error) { errorType, errorCode ->
MarketsDataAnalyticsEvent.List.Error(
errorType = errorType,
errorCode = errorCode,
)
}
}
private fun createDetailsErrorEvent(
error: ApiResponseError,
request: MarketsDataAnalyticsEvent.Details.Error.Request,
tokenSymbol: String,
): MarketsDataAnalyticsEvent.Details.Error {
return createErrorEvent(error) { errorType, errorCode ->
MarketsDataAnalyticsEvent.Details.Error(
errorType = errorType,
errorCode = errorCode,
request = request,
tokenSymbol = tokenSymbol,
)
}
}
private inline fun <T> createErrorEvent(
error: ApiResponseError,
createEvent: (MarketsDataAnalyticsEvent.Type, Int?) -> T,
): T {
return when (error) {
is ApiResponseError.HttpException -> {
createEvent(MarketsDataAnalyticsEvent.Type.Http, error.code.code)
}
is ApiResponseError.TimeoutException -> {
createEvent(MarketsDataAnalyticsEvent.Type.Timeout, null)
}
is ApiResponseError.NetworkException -> {
createEvent(MarketsDataAnalyticsEvent.Type.Network, null)
}
is ApiResponseError.UnknownException -> {
createEvent(MarketsDataAnalyticsEvent.Type.Unknown, null)
}
}
}
}

View file

@ -25,7 +25,7 @@ internal class MarketsBatchUpdateFetcher(
private val marketsApi: TangemTechMarketsApi,
private val tangemTechApi: TangemTechApi,
private val analyticsEventHandler: AnalyticsEventHandler,
private val onApiError: () -> Unit,
private val onApiError: (ApiResponseError) -> Unit,
) : BatchUpdateFetcher<Int, List<TokenMarket>, TokenMarketUpdateRequest> {
override suspend fun BatchUpdateFetcher.UpdateContext<Int, List<TokenMarket>>.fetchUpdateAsync(
@ -124,6 +124,7 @@ internal class MarketsBatchUpdateFetcher(
analyticsEventHandler.send(
MarketsDataAnalyticsEvent.ChartNullValuesError(
requestPath = "coins/history_preview",
errorType = MarketsDataAnalyticsEvent.Type.Custom,
),
)
@ -133,11 +134,11 @@ internal class MarketsBatchUpdateFetcher(
}
}
private inline fun <T> catchApiError(onError: () -> Unit, block: () -> T): T {
private inline fun <T> catchApiError(onError: (ApiResponseError) -> Unit, block: () -> T): T {
return try {
block()
} catch (e: ApiResponseError) {
onError()
onError(e)
throw e
}
}

View file

@ -9,7 +9,16 @@ sealed interface MarketsDataAnalyticsEvent {
params: Map<String, String> = mapOf(),
) : AnalyticsEvent(category = "Markets", event = event, params = params), MarketsDataAnalyticsEvent {
data object Error : List(event = "Data Error")
data class Error(
val errorType: Type,
val errorCode: Int? = null,
) : List(
event = "Data Error",
params = buildMap {
put("Error Type", errorType.value)
errorCode?.let { put("Error Code", it.toString()) }
},
)
}
sealed class Details(
@ -20,12 +29,16 @@ sealed interface MarketsDataAnalyticsEvent {
data class Error(
val request: Request,
val tokenSymbol: String,
val errorType: Type,
val errorCode: Int? = null,
) : Details(
event = "Data Error",
params = mapOf(
"Source" to request.source,
"Token" to tokenSymbol,
),
params = buildMap {
put("Source", request.source)
put("Token", tokenSymbol)
errorCode?.let { put("Error Code", it.toString()) }
put("Error Type", errorType.value)
},
) {
enum class Request(val source: String) {
@ -43,13 +56,25 @@ sealed interface MarketsDataAnalyticsEvent {
data class ChartNullValuesError(
val requestPath: String,
val errorType: Type,
val errorCode: Int? = null,
) : AnalyticsEvent(
category = "Markets / Chart",
event = "Data Error",
params = mapOf("Request path" to requestPath),
error = IllegalStateException(
"Chart data contains null values from the API",
),
params = buildMap {
put("Request path", requestPath)
errorCode?.let { put("Error Code", it.toString()) }
put("Error Type", errorType.value)
put("Error Description", "Chart data contains null values from the API")
},
),
MarketsDataAnalyticsEvent
enum class Type(val value: String) {
Http("Http"),
Timeout("Timeout"),
Network("Network"),
Custom("Custom"),
Unknown("Unknown"),
}
}

View file

@ -45,7 +45,7 @@ internal class DefaultQuotesRepository(
.filterNotNull()
.flatMapLatest { appCurrency ->
fetchExpiredQuotes(currenciesIds, appCurrency.id, refresh = refresh)
quotesStore.get(currenciesIds).map(quotesConverter::convertSet)
quotesStore.get(currenciesIds).map { quotesConverter.convert(currenciesIds to it) }
}
.cancellable()
.flowOn(dispatchers.io)
@ -77,14 +77,15 @@ internal class DefaultQuotesRepository(
val quotes = quotesStore.getSync(currenciesIds)
quotesConverter.convertSet(quotes)
quotesConverter.convert(currenciesIds to quotes)
}
}
override suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote? {
return withContext(dispatchers.io) {
val quote = quotesStore.getSync(setOf(currencyId)).firstOrNull()
quote?.let { quotesConverter.convert(it) }
val setOfCurrencyId = setOf(currencyId)
val quote = quotesStore.getSync(setOfCurrencyId).firstOrNull()
quote?.let { quotesConverter.convert(setOfCurrencyId to setOf(it)).firstOrNull() }
}
}

View file

@ -1,16 +1,28 @@
package com.tangem.data.tokens.utils
import com.tangem.datasource.local.quote.model.StoredQuote
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Quote
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class QuotesConverter : Converter<StoredQuote, Quote> {
typealias QuotesConverterValue = Pair<Set<CryptoCurrency.ID>, Set<StoredQuote>>
override fun convert(value: StoredQuote): Quote {
val (rawCurrencyId, responseQuote) = value
internal class QuotesConverter : Converter<QuotesConverterValue, Set<Quote>> {
return Quote(
override fun convert(value: QuotesConverterValue): Set<Quote> {
val (setOfCurrencyId, setOfStoredQuote) = value
return setOfCurrencyId.mapTo(hashSetOf()) { id ->
setOfStoredQuote.find { id.rawCurrencyId == it.rawCurrencyId }
?.let(::convertExistStoredQuote)
?: Quote.Empty(id.rawCurrencyId)
}
}
private fun convertExistStoredQuote(storedQuote: StoredQuote): Quote {
val (rawCurrencyId, responseQuote) = storedQuote
return Quote.Value(
rawCurrencyId = rawCurrencyId,
fiatRate = responseQuote.price ?: BigDecimal.ZERO,
priceChange = (responseQuote.priceChange24h ?: BigDecimal.ZERO).movePointLeft(2),

View file

@ -15,10 +15,10 @@ class GetCurrencyQuotesUseCase(
currencyID: CryptoCurrency.ID,
interval: PriceChangeInterval,
refresh: Boolean,
): Flow<Option<Quote>> {
): Flow<Option<Quote.Value>> {
return quotesRepository.getQuotesUpdates(
currenciesIds = setOf(currencyID),
refresh = refresh,
).map { it.firstOrNull().toOption() }.catch { emit(None) }
).map { it.filterIsInstance<Quote.Value>().firstOrNull().toOption() }.catch { emit(None) }
}
}

View file

@ -2,15 +2,27 @@ package com.tangem.domain.tokens.model
import java.math.BigDecimal
/**
* Represents financial information for a specific cryptocurrency, including its fiat exchange rate and price change.
*
* @property rawCurrencyId The unique identifier of the cryptocurrency for which the financial information is provided.
* @property fiatRate The current fiat exchange rate for the cryptocurrency.
* @property priceChange The price change for the cryptocurrency.
*/
data class Quote(
val rawCurrencyId: String,
val fiatRate: BigDecimal,
val priceChange: BigDecimal,
)
sealed interface Quote {
val rawCurrencyId: String?
/**
* Represents unknown financial information for a specific cryptocurrency.
*
* @property rawCurrencyId The raw cryptocurrency ID. If it is a custom token, the value will be `null`.
*/
data class Empty(override val rawCurrencyId: String?) : Quote
/**
* Represents financial information for a specific cryptocurrency, including its fiat exchange rate and price change.
*
* @property rawCurrencyId The unique identifier of the cryptocurrency for which the financial information is provided.
* @property fiatRate The current fiat exchange rate for the cryptocurrency.
* @property priceChange The price change for the cryptocurrency.
*/
data class Value(
override val rawCurrencyId: String,
val fiatRate: BigDecimal,
val priceChange: BigDecimal,
) : Quote
}

View file

@ -12,6 +12,18 @@ internal class CurrencyStatusOperations(
private val ignoreQuote: Boolean,
) {
private val Quote?.fiatRate: BigDecimal?
get() = when (this) {
is Quote.Value -> this.fiatRate
is Quote.Empty, null -> null
}
private val Quote?.priceChange: BigDecimal?
get() = when (this) {
is Quote.Value -> this.priceChange
is Quote.Empty, null -> null
}
fun createTokenStatus(): CryptoCurrencyStatus = CryptoCurrencyStatus(currency, createStatus())
private fun createStatus(): CryptoCurrencyStatus.Value {
@ -74,7 +86,7 @@ internal class CurrencyStatusOperations(
null
}
return when {
ignoreQuote -> CryptoCurrencyStatus.NoQuote(
quote is Quote.Empty || ignoreQuote -> CryptoCurrencyStatus.NoQuote(
amount = amount,
hasCurrentNetworkTransactions = hasCurrentNetworkTransactions,
pendingTransactions = currentTransactions,
@ -91,8 +103,7 @@ internal class CurrencyStatusOperations(
networkAddress = status.address,
yieldBalance = currentYieldBalance,
)
quote == null -> CryptoCurrencyStatus.Loading
else -> CryptoCurrencyStatus.Loaded(
quote is Quote.Value -> CryptoCurrencyStatus.Loaded(
amount = amount,
fiatAmount = calculateFiatAmount(amount, quote.fiatRate),
fiatRate = quote.fiatRate,
@ -102,6 +113,7 @@ internal class CurrencyStatusOperations(
networkAddress = status.address,
yieldBalance = currentYieldBalance,
)
else -> CryptoCurrencyStatus.Loading
}
}

View file

@ -7,65 +7,71 @@ import java.math.BigDecimal
@Suppress("MemberVisibilityCanBePrivate")
internal object MockQuotes {
val quote1 = Quote(
val quote1 = Quote.Value(
rawCurrencyId = MockTokens.token1.id.rawCurrencyId!!,
fiatRate = BigDecimal("1.23"),
priceChange = BigDecimal("0.01"),
)
val quote2 = Quote(
val quote2 = Quote.Value(
rawCurrencyId = MockTokens.token2.id.rawCurrencyId!!,
fiatRate = BigDecimal("2.34"),
priceChange = BigDecimal("-0.02"),
)
val quote3 = Quote(
val quote3 = Quote.Value(
rawCurrencyId = MockTokens.token3.id.rawCurrencyId!!,
fiatRate = BigDecimal("3.45"),
priceChange = BigDecimal("0.03"),
)
val quote4 = Quote(
val quote4 = Quote.Value(
rawCurrencyId = MockTokens.token4.id.rawCurrencyId!!,
fiatRate = BigDecimal("4.56"),
priceChange = BigDecimal("-0.04"),
)
val quote5 = Quote(
val quote5 = Quote.Value(
rawCurrencyId = MockTokens.token5.id.rawCurrencyId!!,
fiatRate = BigDecimal("5.67"),
priceChange = BigDecimal("0.05"),
)
val quote6 = Quote(
val quote6 = Quote.Value(
rawCurrencyId = MockTokens.token6.id.rawCurrencyId!!,
fiatRate = BigDecimal("6.78"),
priceChange = BigDecimal("-0.06"),
)
val quote7 = Quote(
val quote7 = Quote.Value(
rawCurrencyId = MockTokens.token7.id.rawCurrencyId!!,
fiatRate = BigDecimal("7.89"),
priceChange = BigDecimal("0.07"),
)
val quote8 = Quote(
val quote8 = Quote.Value(
rawCurrencyId = MockTokens.token8.id.rawCurrencyId!!,
fiatRate = BigDecimal("8.90"),
priceChange = BigDecimal("-0.08"),
)
val quote9 = Quote(
val quote9 = Quote.Value(
rawCurrencyId = MockTokens.token9.id.rawCurrencyId!!,
fiatRate = BigDecimal("9.01"),
priceChange = BigDecimal("0.09"),
)
val quote10 = Quote(
val quote10 = Quote.Value(
rawCurrencyId = MockTokens.token10.id.rawCurrencyId!!,
fiatRate = BigDecimal("10.12"),
priceChange = BigDecimal("-0.10"),
)
val quotes = nonEmptySetOf(quote1, quote2, quote3, quote4, quote5, quote6, quote7, quote8, quote9, quote10)
val quote11 = Quote.Empty(null)
val quote12 = Quote.Empty("null")
val quotes = nonEmptySetOf(
quote1, quote2, quote3, quote4, quote5, quote6, quote7, quote8, quote9, quote10,
quote11, quote12,
)
}

View file

@ -1,10 +1,7 @@
package com.tangem.domain.tokens.mock
import arrow.core.nonEmptyListOf
import com.tangem.domain.tokens.model.CryptoCurrencyAmountStatus
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkAddress
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.tokens.model.*
import java.math.BigDecimal
@Suppress("MemberVisibilityCanBePrivate")
@ -140,20 +137,29 @@ internal object MockTokensStates {
as? CryptoCurrencyAmountStatus.Loaded
)?.value ?: BigDecimal.ZERO
val quote = MockQuotes.quotes.first { it.rawCurrencyId == status.currency.id.rawCurrencyId }
val fiatAmount = amount * quote.fiatRate
status.copy(
value = CryptoCurrencyStatus.Loaded(
val value = when (quote) {
is Quote.Empty -> CryptoCurrencyStatus.NoQuote(
amount = status.value.amount!!,
pendingTransactions = emptySet(),
hasCurrentNetworkTransactions = false,
networkAddress = requireNotNull(
value = MockNetworks.verifiedNetworksStatuses.first { it.network == status.currency.network }.value as? NetworkStatus.Verified,
).address,
yieldBalance = null,
)
is Quote.Value -> CryptoCurrencyStatus.Loaded(
amount = amount,
fiatAmount = fiatAmount,
fiatAmount = amount * quote.fiatRate,
fiatRate = quote.fiatRate,
priceChange = quote.priceChange,
pendingTransactions = emptySet(),
hasCurrentNetworkTransactions = false,
networkAddress = requireNotNull(networkStatus.value as? NetworkStatus.Verified).address,
yieldBalance = null,
),
)
)
}
status.copy(value = value)
}
val noQuotesTokensStatuses = loadedTokensStates.map { status ->

View file

@ -2178,8 +2178,8 @@ internal class SwapInteractorImpl @AssistedInject constructor(
)
}
private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map<CryptoCurrency.ID, Quote> {
val set = ids.toSet().getQuotesOrEmpty(false)
private suspend fun getQuotes(vararg ids: CryptoCurrency.ID): Map<CryptoCurrency.ID, Quote.Value> {
val set = ids.toSet().getQuotesOrEmpty(false).filterIsInstance<Quote.Value>()
return ids
.mapNotNull { id -> set.find { it.rawCurrencyId == id.rawCurrencyId }?.let { id to it } }

View file

@ -3,9 +3,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
import com.tangem.core.ui.utils.toTimeFormat
@ -55,16 +55,22 @@ internal class TokenDetailsSwapTransactionsStateConverter(
.forEach { swapTransaction ->
val toCryptoCurrency = swapTransaction.toCryptoCurrency
val fromCryptoCurrency = swapTransaction.fromCryptoCurrency
val toCryptoCurrencyRawId = swapTransaction.toCryptoCurrency.id.rawCurrencyId
val fromCryptoCurrencyRawId = swapTransaction.fromCryptoCurrency.id.rawCurrencyId
swapTransaction.transactions.forEach { transaction ->
val toAmount = transaction.toCryptoAmount
val fromAmount = transaction.fromCryptoAmount
val toFiatAmount = quotes.firstOrNull {
it.rawCurrencyId == swapTransaction.toCryptoCurrency.id.rawCurrencyId
}?.fiatRate?.multiply(toAmount)
val fromFiatAmount = quotes.firstOrNull {
it.rawCurrencyId == swapTransaction.fromCryptoCurrency.id.rawCurrencyId
}?.fiatRate?.multiply(fromAmount)
var toFiatAmount: BigDecimal? = null
var fromFiatAmount: BigDecimal? = null
quotes.forEach { quote ->
if (quote is Quote.Value && quote.rawCurrencyId == toCryptoCurrencyRawId) {
toFiatAmount = quote.fiatRate.multiply(toAmount)
}
if (quote is Quote.Value && quote.rawCurrencyId == fromCryptoCurrencyRawId) {
fromFiatAmount = quote.fiatRate.multiply(fromAmount)
}
}
val timestamp = transaction.timestamp
val notifications =
getNotification(transaction.status?.status, transaction.status?.txExternalUrl, null)

View file

@ -114,6 +114,10 @@ sealed class WalletScreenAnalyticsEvent {
data object BackupError : MainScreen(event = "Notice - Backup Error")
data object NotePromo : MainScreen(event = "Notice - Note Promo")
data object NotePromoButton : MainScreen(event = "Note Promo Button")
data object UnlockAllWithBiometrics : MainScreen(event = "Button - Unlock All With Biometrics")
data object UnlockWithCardScan : MainScreen(event = "Button - Unlock With Card Scan")

View file

@ -35,6 +35,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
return warnings.mapNotNullTo(mutableSetOf(), ::getEvent)
}
@Suppress("CyclomaticComplexMethod")
private fun getEvent(warning: WalletNotification): AnalyticsEvent? {
return when (warning) {
is WalletNotification.Critical.DevCard -> MainScreen.DevelopmentCard
@ -46,6 +47,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.Informational.MissingAddresses -> MainScreen.MissingAddresses
is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem
is WalletNotification.Critical.BackupError -> MainScreen.BackupError
is WalletNotification.NoteMigration -> MainScreen.NotePromo
is WalletNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX,

View file

@ -41,7 +41,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
buildList {
addCriticalNotifications(cardTypesResolver)
addInformationalNotifications(cardTypesResolver)
addInformationalNotifications(cardTypesResolver, clickIntents)
addWarningNotifications(
userWallet,
@ -75,7 +75,17 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
}
}
private fun MutableList<WalletNotification>.addInformationalNotifications(cardTypesResolver: CardTypesResolver) {
private fun MutableList<WalletNotification>.addInformationalNotifications(
cardTypesResolver: CardTypesResolver,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.NoteMigration(
onClick = { clickIntents.onNoteMigrationButtonClick(NOTE_MIGRATION_URL) },
),
condition = cardTypesResolver.isTangemNote(),
)
addIf(
element = WalletNotification.Informational.DemoCard,
condition = isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
@ -160,13 +170,17 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
if (condition) {
add(element = element)
if (element is WalletNotification.Critical || element is WalletNotification.Warning) {
if (element is WalletNotification.Critical ||
element is WalletNotification.Warning ||
element is WalletNotification.NoteMigration
) {
readyForRateAppNotification = false
}
}
}
private companion object {
const val NOTE_MIGRATION_URL = "https://tangem.com/en/?promocode=Note10"
const val MAX_REMAINING_SIGNATURES_COUNT = 10
}
}

View file

@ -2,10 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.extensions.*
import com.tangem.feature.wallet.impl.R
import org.joda.time.DateTime
@ -143,7 +140,7 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
object DemoCard : Informational(
data object DemoCard : Informational(
title = resourceReference(id = R.string.warning_demo_mode_title),
subtitle = resourceReference(id = R.string.warning_demo_mode_message),
)
@ -204,4 +201,16 @@ sealed class WalletNotification(val config: NotificationConfig) {
onCloseClick = onCloseClick,
),
)
data class NoteMigration(val onClick: () -> Unit) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.wallet_promo_banner_title),
subtitle = resourceReference(R.string.wallet_promo_banner_description),
iconResId = R.drawable.banner_note_migration,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.wallet_promo_banner_button_title),
onClick = onClick,
),
),
)
}

View file

@ -1,9 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.notifications.NoteMigrationNotification
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.OkxPromoNotification
import com.tangem.core.ui.components.notifications.RingPromoNotification
@ -19,7 +19,6 @@ import kotlinx.collections.immutable.ImmutableList
*
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalFoundationApi::class)
internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotification>, modifier: Modifier = Modifier) {
items(
items = configs,
@ -31,16 +30,25 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
is WalletNotification.SwapPromo -> {
OkxPromoNotification(
config = it.config,
modifier = modifier.animateItemPlacement(),
modifier = modifier.animateItem(),
)
}
is WalletNotification.RingPromo -> {
RingPromoNotification(config = it.config, modifier = modifier.animateItemPlacement())
RingPromoNotification(
config = it.config,
modifier = modifier.animateItem(),
)
}
is WalletNotification.NoteMigration -> {
NoteMigrationNotification(
config = it.config,
modifier = modifier.animateItem(),
)
}
else -> {
Notification(
config = it.config,
modifier = modifier.animateItemPlacement(),
modifier = modifier.animateItem(),
iconTint = when (it) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent

View file

@ -67,6 +67,8 @@ internal interface WalletWarningsClickIntents {
fun onCloseTravalaPromoClick()
fun onSupportClick()
fun onNoteMigrationButtonClick(url: String)
}
@Suppress("LongParameterList")
@ -296,6 +298,13 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onNoteMigrationButtonClick(url: String) {
analyticsEventHandler.send(MainScreen.NotePromoButton)
viewModelScope.launch(dispatchers.main) {
router.openUrl(url)
}
}
private fun getSelectedUserWallet(): UserWallet? {
val userWalletId = stateHolder.getSelectedWalletId()
return getUserWalletUseCase(userWalletId).getOrElse {

View file

@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs = -Xmx6144m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs = -Xmx6144m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects

View file

@ -87,7 +87,7 @@ markdownComposeView = "0.5.4"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "release-app_5.18-869"
tangemBlockchainSdk = "release-app_5.18-870"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "release-app_5.18-402"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^