Updated on 2026-08-14
This commit is contained in:
commit
253278ea49
30 changed files with 985 additions and 159 deletions
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="_warning_stelar_token_required_min_coin_reserve">Erforderliche Mindest-XLM-Reserve %1$s</string>
|
||||
<string name="action_buttons_buy_empty_search_message">Du kannst Deinen Token nicht finden? Gehe zum Bereich „Markt“ auf der Hauptseite und fügen diesen zum kaufen im Portfolio hinzu.</string>
|
||||
<string name="action_buttons_sell_empty_search_message">Du kannst Deinen Token nicht finden? Gehe zum Bereich „Markt“ auf der Hauptseite und fügen diesen zum verkaufen im Portfolio hinzu.</string>
|
||||
<string name="action_buttons_sell_navigation_bar_title">Verkaufen</string>
|
||||
|
|
@ -65,6 +66,10 @@
|
|||
<item quantity="one">%d Gerät</item>
|
||||
<item quantity="other">%d Geräte</item>
|
||||
</plurals>
|
||||
<plurals name="card_label_token_count">
|
||||
<item quantity="one">Token</item>
|
||||
<item quantity="other">Tokens</item>
|
||||
</plurals>
|
||||
<string name="card_settings_access_code_recovery_disabled_description">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.</string>
|
||||
<string name="card_settings_access_code_recovery_enabled_description">Ermöglicht die Verwendung dieser Karte oder Ring zum Zurücksetzen des Zugangscodes auf anderen Karten oder Ringe in dieser Brieftasche</string>
|
||||
<string name="card_settings_access_code_recovery_title">Zugangspin resetten</string>
|
||||
|
|
@ -110,6 +115,7 @@
|
|||
<string name="common_cancel">Abbrechen</string>
|
||||
<string name="common_change">Ändern</string>
|
||||
<string name="common_choose_action">Aktion wählen</string>
|
||||
<string name="common_choose_token">Token auswählen</string>
|
||||
<string name="common_claim">Beanspruchen</string>
|
||||
<string name="common_claim_rewards">Stakingbelohnungen beanspruchen</string>
|
||||
<string name="common_close">Schließen</string>
|
||||
|
|
@ -151,6 +157,7 @@
|
|||
<string name="common_generate_addresses">Adressen synchronisieren</string>
|
||||
<string name="common_go_to_provider">Zum Anbieter gehen</string>
|
||||
<string name="common_go_to_token">Zum Token</string>
|
||||
<string name="common_got_it">Verstanden</string>
|
||||
<string name="common_hide">Ausblenden</string>
|
||||
<string name="common_hour">stunde</string>
|
||||
<string name="common_import">Importieren</string>
|
||||
|
|
@ -205,6 +212,7 @@
|
|||
<string name="common_submit">Einreichen</string>
|
||||
<string name="common_success">Erfolgt</string>
|
||||
<string name="common_support">Unterstützung</string>
|
||||
<string name="common_supported_networks">Unterstützte Netzwerke</string>
|
||||
<string name="common_swap">Tauschen</string>
|
||||
<string name="common_terms_and_conditions">Allgemeine Geschäftsbedingungen</string>
|
||||
<string name="common_terms_of_use">Nutzungsbedingungen</string>
|
||||
|
|
@ -264,6 +272,7 @@
|
|||
<string name="details_row_subtitle_signed_hashes_format">%s Hasch</string>
|
||||
<string name="details_row_title_cid">Geräte-ID</string>
|
||||
<string name="details_row_title_contact_to_support">Kontakt zum Support</string>
|
||||
<string name="details_row_title_contact_to_support_chat">Support-Chat öffnen</string>
|
||||
<string name="details_row_title_create_backup">Weitere Karten oder Ringe verknüpfen</string>
|
||||
<string name="details_row_title_currency">App Währung</string>
|
||||
<string name="details_row_title_flip_to_hide">Flipp um Guthaben auszublenden</string>
|
||||
|
|
@ -701,10 +710,13 @@
|
|||
<string name="organize_tokens_sort_by_balance">Nach Guthaben</string>
|
||||
<string name="organize_tokens_title">Token organisieren</string>
|
||||
<string name="organize_tokens_ungroup">Gruppe löschen</string>
|
||||
<string name="push_notifications_more_info">Mehr Infos</string>
|
||||
<string name="push_notifications_permission_alert_description">Du kannst Benachrichtigungen für Tangem in den Einstellungen aktivieren.</string>
|
||||
<string name="push_notifications_permission_alert_negative_button">Später aktivieren</string>
|
||||
<string name="push_notifications_permission_alert_positive_button">Einstellungen</string>
|
||||
<string name="push_notifications_permission_alert_title">Benachrichtigungen aktivieren</string>
|
||||
<string name="push_transactions_notifications_description">Erhalte Benachrichtigungen über eingehende Transaktionen der unterstützten Netzwerke</string>
|
||||
<string name="push_transactions_notifications_title">Transaktionsbenachrichtigungen</string>
|
||||
<string name="qr_scanner_camera_denied_gallery_button">Wählen aus der Galerie aus</string>
|
||||
<string name="qr_scanner_camera_denied_settings_button">Einstellungen</string>
|
||||
<string name="qr_scanner_camera_denied_text">Du hast keinen Zugriff auf deine Kamera gewährt</string>
|
||||
|
|
@ -928,7 +940,7 @@
|
|||
<string name="staking_notification_restake_text">Mit Restake kannst Du Dein Guthaben von einem Validator zu einem anderen verschieben, ohne dass Du den Stake aufheben musst.</string>
|
||||
<string name="staking_notification_stake_entire_balance_text">Du bist dabei, Dein gesamtes Guthaben zu staken. Wir empfehlen, einen kleinen Betrag übrig zu lassen, um die Netzwerkgebühren für die Aufhebung des Stakes oder das Einfordern von Prämien abzudecken.</string>
|
||||
<string name="staking_notification_ton_activate_account">Um mit dem Staking bei TON zu beginnen, führe zunächst eine ausgehende Transaktion in beliebiger Höhe durch – dadurch wird Deine Wallet aktiviert.</string>
|
||||
<string name="staking_notification_ton_extra_reserve_info">Zusätzlich zur Netzwerkgebühr werden bis zu 0,2 TON benötigt, um alle Schritte der Transaktion abzuschließen. Nicht genutzte Beträge werden nach Abschluss der Transaktion zurückerstattet.</string>
|
||||
<string name="staking_notification_ton_extra_reserve_info">Für den Abschluss der Transaktion können zusätzlich zur Netzwerkgebühr bis zu 0,2 TON erforderlich sein. Nicht genutzte Beträge werden zurückerstattet.</string>
|
||||
<string name="staking_notification_ton_extra_reserve_is_required">Für diesen Vorgang sind zusätzlich zur Netzwerkgebühr 0,2 TON erforderlich. Bitte lade Dein Guthaben auf.</string>
|
||||
<string name="staking_notification_ton_extra_reserve_title">0,2 Ton reserviert</string>
|
||||
<string name="staking_notification_ton_have_to_unstake_all_text">Durch diese Aktion werden andere Positionen geschlossen oder gemäß den Netzwerkregeln in den Auszahlungsstatus versetzt.</string>
|
||||
|
|
@ -1071,6 +1083,8 @@
|
|||
<string name="transaction_history_transaction_from_address">von: %s</string>
|
||||
<string name="transaction_history_transaction_to_address">zu: %s</string>
|
||||
<string name="transaction_history_transaction_validator">Validierer: %s</string>
|
||||
<string name="transaction_notifications_warning_description">Benachrichtigungen sind aktiviert, funktionieren aber erst, wenn Du Benachrichtigungen in Deinen Geräteeinstellungen zulässt.</string>
|
||||
<string name="transaction_notifications_warning_title">Transaktionsbenachrichtigungen</string>
|
||||
<string name="transfer_min_amount_error">Minimum %s</string>
|
||||
<string name="transfer_notification_invalid_minimum_transaction_amount_text">Der Mindesttransaktionsbetrag beträgt %1$s.</string>
|
||||
<string name="tron_will_be_send_token_fee_description">Die Netzwerkgebühren für beliebte Token im Tron-Netzwerk können höher sein. Das Staking von TRX kann helfen, die Transaktionskosten zu senken.</string>
|
||||
|
|
@ -1087,10 +1101,13 @@
|
|||
<string name="twins_recreate_toolbar">Tangem Twin</string>
|
||||
<string name="twins_recreate_warning">Diese Aktion ist unumkehrbar. Du hast keinen Zugriff mehr auf die alte Wallet.</string>
|
||||
<string name="twins_scan_twin_with_number">Tippe auf die Doppelkarte oder Ring mit der Nummer %s und entferne sie erst am Ende des Vorgangs.</string>
|
||||
<string name="unexpected_error_description">Bitte versuche es später noch einmal. Sollte das Problem weiterhin bestehen, wende Dich bitte an den Support.</string>
|
||||
<string name="unexpected_error_title">Etwas ist schiefgelaufen!</string>
|
||||
<string name="universal_error">Es ist ein Fehler aufgetreten. Fehlercode: %s. Bitte kontaktiere unseren Support.</string>
|
||||
<string name="unlock_wallet_description_full">Verwende %s oder scanne eine Karte oder Ring, um Zugriff auf deine Wallet zu erhalten.</string>
|
||||
<string name="unsupported_wc_version">Verbindung fehlgeschlagen: Diese dApp verwendet Wallet Connect Version 1.0, die nicht unterstützt wird. Bitte stelle sicher, dass die dApp Wallet Connect Version 2.0 unterstützt, um eine erfolgreiche Verbindung herzustellen.</string>
|
||||
<string name="user_push_notification_agreement_argument_one">Bleib auf dem Laufenden mit den neuesten Funktionen und Neuigkeiten</string>
|
||||
<string name="user_push_notification_agreement_argument_three">Genehmigung für Transaktionen anfordern</string>
|
||||
<string name="user_push_notification_agreement_argument_two">Sei der Erste, der von neuen Aktionen erfährt</string>
|
||||
<string name="user_push_notification_agreement_header">Möchtest du Push-Benachrichtigungen verwenden?</string>
|
||||
<string name="user_wallet_list_add_button">Neues Wallet hinzufügen</string>
|
||||
|
|
@ -1170,7 +1187,7 @@
|
|||
<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_push_notifications_description">Bleib stets über eingehende Wallet-Transaktionen und Tangem-Updates auf dem Laufenden.</string>
|
||||
<string name="wallet_settings_push_notifications_title">Push-Benachrichtigungen</string>
|
||||
<string name="wallet_settings_push_notifications_title">Transaktionsbenachrichtigungen</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>
|
||||
|
|
@ -1240,6 +1257,7 @@
|
|||
<string name="warning_rate_app_message">Deine Bewertung motiviert uns, die Tangem Wallet noch besser zu machen.</string>
|
||||
<string name="warning_rate_app_title">Gefällt dir Tangem?</string>
|
||||
<string name="warning_receive_blocked_hedera_token_association_required_message">Du musst deinen Token zuordnen, bevor du Token erhalten kannst</string>
|
||||
<string name="warning_receive_blocked_stelar_token_trustline_required_message">Du musst die trustline öffnen, bevor Du Token erhältst</string>
|
||||
<string name="warning_rent_fee_title">Netzmietgebühr erforderlich</string>
|
||||
<string name="warning_seedphrase_action_required_title">Handlungsbedarf</string>
|
||||
<string name="warning_seedphrase_contacted_support">Hast Du innerhalb von 7 Tagen nach der Erstellung Ihres Wallets über die App Kontakt zum Support aufgenommen? Wenn ja oder wenn Du Dir nicht sicher bist, klicke auf „Ja“ und folge den Anweisungen.</string>
|
||||
|
|
@ -1255,6 +1273,9 @@
|
|||
<string name="warning_some_networks_unreachable_message">Wischen Sie nach unten, um zu aktualisieren, oder versuchen Sie es später erneut.</string>
|
||||
<string name="warning_some_networks_unreachable_title">Einige Netzwerke sind nicht erreichbar</string>
|
||||
<string name="warning_some_token_balances_not_updated">Einige Token-Guthaben konnten nicht aktualisiert werden</string>
|
||||
<string name="warning_stellar_token_trustline_button_title">Trustline öffnen</string>
|
||||
<string name="warning_stellar_token_trustline_subtiile">Trustline-Untermenü öffnen</string>
|
||||
<string name="warning_stellar_token_trustline_tiile">Trustline-Titel öffnen</string>
|
||||
<string name="warning_testnet_card_message">Dies ist eine Testnet-Karte. Sie kann keine Transaktionen verarbeiten und sollte nur zu Test- und Entwicklungszwecken verwendet werden.</string>
|
||||
<string name="warning_testnet_card_title">Nur für Testzwecke</string>
|
||||
<string name="warning_token_balance_not_updated">Der Kontostand ist möglicherweise veraltet. Aktualisiere bittre die Seite.</string>
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@
|
|||
<string name="common_cancel">Cancelar</string>
|
||||
<string name="common_change">Cambie</string>
|
||||
<string name="common_choose_action">Elige una acción</string>
|
||||
<string name="common_choose_token">Elige token</string>
|
||||
<string name="common_claim">Reclamar</string>
|
||||
<string name="common_claim_rewards">Reclame recompensas</string>
|
||||
<string name="common_close">Cerrar</string>
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@
|
|||
<string name="common_cancel">Annuler</string>
|
||||
<string name="common_change">Changez</string>
|
||||
<string name="common_choose_action">Choisissez une action</string>
|
||||
<string name="common_choose_token">Choisir le jeton</string>
|
||||
<string name="common_claim">Réclamer</string>
|
||||
<string name="common_claim_rewards">Réclamez des récompenses</string>
|
||||
<string name="common_close">Fermer</string>
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@
|
|||
<string name="common_cancel">キャンセル</string>
|
||||
<string name="common_change">変更</string>
|
||||
<string name="common_choose_action">アクションを選択</string>
|
||||
<string name="common_choose_token">トークンを選択</string>
|
||||
<string name="common_claim">請求</string>
|
||||
<string name="common_claim_rewards">報酬を受け取る</string>
|
||||
<string name="common_close">閉じる</string>
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@
|
|||
<string name="common_cancel">Отмена</string>
|
||||
<string name="common_change">Изменить</string>
|
||||
<string name="common_choose_action">Выберите действие</string>
|
||||
<string name="common_choose_token">Выберите токен</string>
|
||||
<string name="common_claim">Получить</string>
|
||||
<string name="common_claim_rewards">Вывести награду</string>
|
||||
<string name="common_close">Закрыть</string>
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@
|
|||
<string name="common_cancel">Скасувати</string>
|
||||
<string name="common_change">Змінити</string>
|
||||
<string name="common_choose_action">Виберіть дію</string>
|
||||
<string name="common_choose_token">Оберіть токен</string>
|
||||
<string name="common_claim">Отримати</string>
|
||||
<string name="common_claim_rewards">Отримати винагороди</string>
|
||||
<string name="common_close">Закрити</string>
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
<string name="common_buy">購買</string>
|
||||
<string name="common_camera_denied_alert_message">您尚未授予相機訪問權限,請更改您的隱私設置</string>
|
||||
<string name="common_cancel">删除</string>
|
||||
<string name="common_choose_token">選擇代幣</string>
|
||||
<string name="common_close">關閉</string>
|
||||
<string name="common_continue">繼續</string>
|
||||
<string name="common_copy">複製</string>
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@
|
|||
<string name="common_cancel">Cancel</string>
|
||||
<string name="common_change">Change</string>
|
||||
<string name="common_choose_action">Choose action</string>
|
||||
<string name="common_choose_token">Choose token</string>
|
||||
<string name="common_claim">Claim</string>
|
||||
<string name="common_claim_rewards">Claim rewards</string>
|
||||
<string name="common_close">Close</string>
|
||||
|
|
@ -871,6 +872,8 @@
|
|||
<string name="send_validation_invalid_amount">Invalid amount</string>
|
||||
<string name="send_validation_invalid_fee">Fee exceeds balance</string>
|
||||
<string name="send_validation_invalid_total">Total amount exceeds balance</string>
|
||||
<string name="send_with_swap_notification_text">Send any token, and we’ll convert it on the way. Your recipient gets exactly what they need—seamlessly.</string>
|
||||
<string name="send_with_swap_title">Send with swap</string>
|
||||
<string name="sent_transaction_sent_title">Transaction sent</string>
|
||||
<string name="settings_card_settings_footer">Prepare to scan card or ring you want to set up.</string>
|
||||
<string name="settings_forget_wallet">Forget wallet</string>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
|
|
@ -8,4 +9,7 @@ dependencies {
|
|||
/* Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
|
||||
/** Other */
|
||||
implementation(deps.kotlin.serialization)
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ package com.tangem.domain.managetokens.model
|
|||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class ManagedCryptoCurrency {
|
||||
|
||||
abstract val id: ID
|
||||
|
|
@ -10,6 +12,7 @@ sealed class ManagedCryptoCurrency {
|
|||
abstract val symbol: String
|
||||
abstract val iconUrl: String?
|
||||
|
||||
@Serializable
|
||||
sealed class Custom : ManagedCryptoCurrency() {
|
||||
|
||||
abstract val currencyId: CryptoCurrency.ID
|
||||
|
|
@ -18,6 +21,7 @@ sealed class ManagedCryptoCurrency {
|
|||
override val id: ID
|
||||
get() = ID(currencyId.value)
|
||||
|
||||
@Serializable
|
||||
data class Token(
|
||||
override val currencyId: CryptoCurrency.ID,
|
||||
override val name: String,
|
||||
|
|
@ -28,6 +32,7 @@ sealed class ManagedCryptoCurrency {
|
|||
val decimals: Int,
|
||||
) : Custom()
|
||||
|
||||
@Serializable
|
||||
data class Coin(
|
||||
override val currencyId: CryptoCurrency.ID,
|
||||
override val name: String,
|
||||
|
|
@ -37,6 +42,7 @@ sealed class ManagedCryptoCurrency {
|
|||
) : Custom()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Token(
|
||||
override val id: ID,
|
||||
override val name: String,
|
||||
|
|
@ -50,8 +56,10 @@ sealed class ManagedCryptoCurrency {
|
|||
}
|
||||
|
||||
@JvmInline
|
||||
@Serializable
|
||||
value class ID(val value: String)
|
||||
|
||||
@Serializable
|
||||
sealed class SourceNetwork {
|
||||
|
||||
abstract val network: Network
|
||||
|
|
@ -77,12 +85,14 @@ sealed class ManagedCryptoCurrency {
|
|||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Main(
|
||||
override val network: Network,
|
||||
override val decimals: Int,
|
||||
val isL2Network: Boolean,
|
||||
) : SourceNetwork()
|
||||
|
||||
@Serializable
|
||||
data class Default(
|
||||
override val network: Network,
|
||||
override val decimals: Int,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ android {
|
|||
|
||||
dependencies {
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.manageTokens.models)
|
||||
|
||||
/* Project - Core */
|
||||
implementation(projects.core.ui)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.features.managetokens.component
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
interface ChooseManagedTokensComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val initialCurrency: CryptoCurrency,
|
||||
val source: Source,
|
||||
)
|
||||
|
||||
enum class Source {
|
||||
SendViaSwap,
|
||||
}
|
||||
|
||||
interface Factory : ComponentFactory<Params, ChooseManagedTokensComponent>
|
||||
}
|
||||
|
|
@ -4,4 +4,5 @@ enum class ManageTokensSource(val analyticsName: String) {
|
|||
STORIES(analyticsName = "Stories"),
|
||||
ONBOARDING(analyticsName = "Onboarding"),
|
||||
SETTINGS(analyticsName = "Settings"),
|
||||
SEND_VIA_SWAP(analyticsName = "SendViaSwap"),
|
||||
}
|
||||
|
|
@ -18,9 +18,10 @@ dependencies {
|
|||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.ui)
|
||||
|
||||
/* Project - Domain */
|
||||
implementation(projects.domain.card)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.features.managetokens.choosetoken
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
|
||||
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensModel
|
||||
import com.tangem.features.managetokens.choosetoken.ui.ChooseManagedTokenContent
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultChooseManagedTokensComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: ChooseManagedTokensComponent.Params,
|
||||
) : ChooseManagedTokensComponent, AppComponentContext by context {
|
||||
|
||||
private val model: ChooseManagedTokensModel = getOrCreateModel(params)
|
||||
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = ChooseManageTokensBottomSheetConfig.serializer(),
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val uiState by model.uiState.collectAsStateWithLifecycle()
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
|
||||
ChooseManagedTokenContent(
|
||||
state = uiState,
|
||||
)
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun bottomSheetChild(
|
||||
config: ChooseManageTokensBottomSheetConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
else -> getStubComponent()
|
||||
}
|
||||
|
||||
private fun getStubComponent() = StubComponent()
|
||||
|
||||
class StubComponent : ComposableBottomSheetComponent {
|
||||
override fun dismiss() {}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : ChooseManagedTokensComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: ChooseManagedTokensComponent.Params,
|
||||
): DefaultChooseManagedTokensComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.managetokens.choosetoken.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.managetokens.choosetoken.DefaultChooseManagedTokensComponent
|
||||
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensModel
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(ModelComponent::class)
|
||||
@Module
|
||||
internal interface ChooseManagedTokensModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(ChooseManagedTokensModel::class)
|
||||
fun provideChooseManagedTokensModel(impl: ChooseManagedTokensModel): Model
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ChooseManagedTokensModuleBinds {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun provideChooseManagedTokensComponent(
|
||||
impl: DefaultChooseManagedTokensComponent.Factory,
|
||||
): ChooseManagedTokensComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.managetokens.choosetoken.entity
|
||||
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed class ChooseManageTokensBottomSheetConfig {
|
||||
|
||||
@Serializable
|
||||
data class SwapTokensBottomSheetConfig(
|
||||
val userWalletId: UserWalletId,
|
||||
val initialCurrency: CryptoCurrency,
|
||||
val token: ManagedCryptoCurrency.Token,
|
||||
) : ChooseManageTokensBottomSheetConfig()
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.features.managetokens.choosetoken.entity
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
|
||||
|
||||
internal data class ChooseManagedTokenUM(
|
||||
val notificationUM: NotificationUM?,
|
||||
val readContent: ManageTokensUM.ReadContent,
|
||||
)
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
package com.tangem.features.managetokens.choosetoken.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
|
||||
import com.tangem.features.managetokens.choosetoken.entity.ChooseManagedTokenUM
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent.Source
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.utils.list.ManageTokensListManager
|
||||
import com.tangem.features.managetokens.utils.list.getLoadingItems
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.isNotEmpty
|
||||
|
||||
@ModelScoped
|
||||
internal class ChooseManagedTokensModel @Inject constructor(
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
paramsContainer: ParamsContainer,
|
||||
manageTokensListManagerFactory: ManageTokensListManager.Factory,
|
||||
) : Model() {
|
||||
|
||||
private val params: ChooseManagedTokensComponent.Params = paramsContainer.require()
|
||||
|
||||
private val manageTokensListManager = manageTokensListManagerFactory.create(
|
||||
onCurrencySelect = { token ->
|
||||
bottomSheetNavigation.activate(
|
||||
ChooseManageTokensBottomSheetConfig.SwapTokensBottomSheetConfig(
|
||||
userWalletId = params.userWalletId,
|
||||
initialCurrency = params.initialCurrency,
|
||||
token = token,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<ChooseManageTokensBottomSheetConfig> = SlotNavigation()
|
||||
val uiState: StateFlow<ChooseManagedTokenUM>
|
||||
field = MutableStateFlow<ChooseManagedTokenUM>(createReadContentModel())
|
||||
|
||||
init {
|
||||
manageTokensListManager.uiItems
|
||||
.onEach { items -> updateItems(items) }
|
||||
.launchIn(modelScope)
|
||||
|
||||
manageTokensListManager.paginationStatus
|
||||
.onEach { status -> updatePaginationStatus(status) }
|
||||
.launchIn(modelScope)
|
||||
|
||||
observeSearchQueryChanges()
|
||||
|
||||
modelScope.launch {
|
||||
manageTokensListManager.launchPagination(source = ManageTokensSource.SEND_VIA_SWAP, userWalletId = null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createReadContentModel(): ChooseManagedTokenUM {
|
||||
return ChooseManagedTokenUM(
|
||||
notificationUM = getNotification(),
|
||||
readContent = ManageTokensUM.ReadContent(
|
||||
popBack = router::pop,
|
||||
isInitialBatchLoading = true,
|
||||
isNextBatchLoading = false,
|
||||
items = getLoadingItems(),
|
||||
topBar = ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(R.string.common_choose_token),
|
||||
onBackButtonClick = router::pop,
|
||||
),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
onQueryChange = ::searchCurrencies,
|
||||
isActive = false,
|
||||
onActiveChange = ::toggleSearchBar,
|
||||
),
|
||||
loadMore = ::loadMoreItems,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotification(): NotificationUM? {
|
||||
return when (params.source) {
|
||||
Source.SendViaSwap -> ChooseManagedTokensNotificationUM.SendViaSwap(
|
||||
onCloseClick = ::removeNotification,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeNotification() {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
notificationUM = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
private fun observeSearchQueryChanges() {
|
||||
uiState
|
||||
.distinctUntilChanged { old, new ->
|
||||
// It's also used to skip search activation to avoid searching an empty query
|
||||
old.readContent.search.query == new.readContent.search.query && new.readContent.search.isActive
|
||||
}
|
||||
.transform { state ->
|
||||
val query = state.readContent.search.query
|
||||
|
||||
if (state.readContent.search.isActive) {
|
||||
emit(query)
|
||||
}
|
||||
}
|
||||
.sample(periodMillis = 1_000)
|
||||
.onEach { query -> manageTokensListManager.search(userWalletId = null, query = query) }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun updateItems(items: ImmutableList<CurrencyItemUM>) {
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
readContent = state.readContent.copy(items = items),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumeScrollToTopEvent() {
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
readContent = state.readContent.copy(scrollToTop = consumedEvent()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePaginationStatus(status: PaginationStatus<*>) {
|
||||
uiState.update { state ->
|
||||
val readContent = state.readContent
|
||||
when (status) {
|
||||
is PaginationStatus.None,
|
||||
is PaginationStatus.InitialLoading,
|
||||
-> {
|
||||
if (readContent.search.isActive) {
|
||||
state.copy(
|
||||
readContent = readContent.copy(items = getLoadingItems()),
|
||||
)
|
||||
} else {
|
||||
state.copy(
|
||||
readContent = readContent.copy(
|
||||
items = getLoadingItems(),
|
||||
isInitialBatchLoading = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is PaginationStatus.NextBatchLoading -> state.copy(
|
||||
readContent = readContent.copy(isNextBatchLoading = true),
|
||||
)
|
||||
is PaginationStatus.InitialLoadingError -> {
|
||||
val message = SnackbarMessage(
|
||||
message = status.throwable.localizedMessage
|
||||
?.let(::stringReference)
|
||||
?: resourceReference(R.string.common_error),
|
||||
)
|
||||
uiMessageSender.send(message)
|
||||
|
||||
state.copy(
|
||||
readContent = readContent.copy(
|
||||
isInitialBatchLoading = false,
|
||||
isNextBatchLoading = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
is PaginationStatus.Paginating -> {
|
||||
(status.lastResult as? BatchFetchResult.Error)?.let { fetchError ->
|
||||
Timber.e(fetchError.throwable)
|
||||
}
|
||||
|
||||
state.copy(
|
||||
readContent = readContent.copy(
|
||||
isInitialBatchLoading = false,
|
||||
isNextBatchLoading = false,
|
||||
scrollToTop = if (readContent.isInitialBatchLoading && readContent.items.isNotEmpty()) {
|
||||
triggeredEvent(
|
||||
data = Unit,
|
||||
onConsume = ::consumeScrollToTopEvent,
|
||||
)
|
||||
} else {
|
||||
readContent.scrollToTop
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
is PaginationStatus.EndOfPagination -> {
|
||||
state.copy(
|
||||
readContent = readContent.copy(
|
||||
isInitialBatchLoading = false,
|
||||
isNextBatchLoading = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchCurrencies(query: String) {
|
||||
uiState.update { state ->
|
||||
state.copy(
|
||||
readContent = state.readContent.copy(
|
||||
search = state.readContent.search.copy(
|
||||
query = query,
|
||||
isActive = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSearchBar(isActive: Boolean) {
|
||||
uiState.update { state ->
|
||||
@StringRes val placeholderTextRes = if (isActive) {
|
||||
R.string.manage_tokens_search_placeholder
|
||||
} else {
|
||||
R.string.common_search
|
||||
}
|
||||
|
||||
state.copy(
|
||||
readContent = state.readContent.copy(
|
||||
search = state.readContent.search.copy(
|
||||
placeholderText = resourceReference(placeholderTextRes),
|
||||
isActive = isActive,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadMoreItems(): Boolean {
|
||||
val state = uiState.value
|
||||
if (state.readContent.isInitialBatchLoading || state.readContent.isNextBatchLoading) return false
|
||||
|
||||
modelScope.launch {
|
||||
manageTokensListManager.loadMore(userWalletId = null, query = state.readContent.search.query)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.managetokens.choosetoken.model
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
|
||||
internal object ChooseManagedTokensNotificationUM {
|
||||
|
||||
data class SendViaSwap(
|
||||
val onCloseClick: () -> Unit,
|
||||
) : NotificationUM.Info(
|
||||
title = resourceReference(R.string.send_with_swap_title),
|
||||
subtitle = resourceReference(R.string.send_with_swap_notification_text),
|
||||
iconResId = R.drawable.ic_exchange_horizontal_24,
|
||||
onCloseClick = onCloseClick,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
package com.tangem.features.managetokens.choosetoken.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.*
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.list.InfiniteListHandler
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.rows.ChainRow
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.features.managetokens.choosetoken.entity.ChooseManagedTokenUM
|
||||
import com.tangem.features.managetokens.choosetoken.model.ChooseManagedTokensNotificationUM
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.ui.*
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
@Composable
|
||||
internal fun ChooseManagedTokenContent(state: ChooseManagedTokenUM, modifier: Modifier = Modifier) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
containerColor = TangemTheme.colors.background.tertiary,
|
||||
contentWindowInsets = WindowInsetsZero,
|
||||
topBar = {
|
||||
ManageTokensTopBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
topBar = state.readContent.topBar,
|
||||
search = state.readContent.search,
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
Content(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize(),
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: ChooseManagedTokenUM, modifier: Modifier = Modifier) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Currencies(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
listState = listState,
|
||||
notificationUM = state.notificationUM,
|
||||
items = state.readContent.items,
|
||||
showLoadingItem = state.readContent.isNextBatchLoading,
|
||||
onLoadMore = state.readContent.loadMore,
|
||||
)
|
||||
}
|
||||
|
||||
EventEffect(event = state.readContent.scrollToTop) {
|
||||
listState.animateScrollToItem(index = 0)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Currencies(
|
||||
listState: LazyListState,
|
||||
notificationUM: NotificationUM?,
|
||||
items: ImmutableList<CurrencyItemUM>,
|
||||
showLoadingItem: Boolean,
|
||||
onLoadMore: () -> Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val bottomBarHeight = with(LocalDensity.current) {
|
||||
WindowInsets.systemBars.getBottom(density = this).toDp()
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier.padding(horizontal = 16.dp),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(
|
||||
bottom = TangemTheme.dimens.spacing76 + bottomBarHeight,
|
||||
),
|
||||
) {
|
||||
if (notificationUM?.config != null) {
|
||||
item(key = "notification_key") {
|
||||
Notification(
|
||||
config = notificationUM.config,
|
||||
iconTint = TangemTheme.colors.icon.accent,
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.padding(bottom = 12.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
contentItems(items)
|
||||
|
||||
if (showLoadingItem) {
|
||||
item(key = "loading_item") {
|
||||
ProgressIndicator(
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InfiniteListHandler(
|
||||
listState = listState,
|
||||
buffer = LOAD_ITEMS_BUFFER,
|
||||
onLoadMore = onLoadMore,
|
||||
)
|
||||
}
|
||||
|
||||
private fun LazyListScope.contentItems(items: ImmutableList<CurrencyItemUM>) {
|
||||
itemsIndexed(
|
||||
items = items,
|
||||
key = { index, item -> item.id.value },
|
||||
) { index, item ->
|
||||
when (item) {
|
||||
is CurrencyItemUM.Basic -> {
|
||||
ChainRow(
|
||||
model = with(item) {
|
||||
ChainRowUM(
|
||||
name = name,
|
||||
type = symbol,
|
||||
icon = icon,
|
||||
showCustom = false,
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = items.lastIndex,
|
||||
)
|
||||
.clickable(onClick = item.onExpandClick)
|
||||
.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
is CurrencyItemUM.Loading -> {
|
||||
LoadingItem(
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = items.lastIndex,
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
is CurrencyItemUM.SearchNothingFound -> {
|
||||
SearchNothingFoundText(
|
||||
modifier = Modifier
|
||||
.roundedShapeItemDecoration(
|
||||
currentIndex = index,
|
||||
addDefaultPadding = false,
|
||||
lastIndex = items.lastIndex,
|
||||
)
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.fillParentMaxSize(),
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun ChooseManagedTokenContent_Preview() {
|
||||
TangemThemePreview {
|
||||
ChooseManagedTokenContent(
|
||||
state = ChooseManagedTokenUM(
|
||||
notificationUM = ChooseManagedTokensNotificationUM.SendViaSwap({}),
|
||||
readContent = ManageTokensUM.ReadContent(
|
||||
popBack = {},
|
||||
isInitialBatchLoading = false,
|
||||
isNextBatchLoading = false,
|
||||
items = buildList {
|
||||
repeat(10) {
|
||||
add(
|
||||
CurrencyItemUM.Basic(
|
||||
id = ManagedCryptoCurrency.ID(
|
||||
value = "ID+$it",
|
||||
),
|
||||
name = "Bitcoin",
|
||||
symbol = "BTC",
|
||||
icon = CurrencyIconState.Loading,
|
||||
networks = CurrencyItemUM.Basic.NetworksUM.Collapsed,
|
||||
onExpandClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList(),
|
||||
topBar = ManageTokensTopBarUM.ReadContent(
|
||||
title = resourceReference(R.string.common_choose_token),
|
||||
onBackButtonClick = {},
|
||||
),
|
||||
search = SearchBarUM(
|
||||
placeholderText = resourceReference(R.string.common_search),
|
||||
query = "",
|
||||
onQueryChange = {},
|
||||
isActive = false,
|
||||
onActiveChange = {},
|
||||
),
|
||||
loadMore = { true },
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
@ -46,16 +46,18 @@ import javax.inject.Inject
|
|||
internal class ManageTokensModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val manageTokensListManager: ManageTokensListManager,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
|
||||
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
manageTokensListManagerFactory: ManageTokensListManager.Factory,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: ManageTokensComponent.Params = paramsContainer.require()
|
||||
|
||||
private val manageTokensListManager = manageTokensListManagerFactory.create()
|
||||
|
||||
val state: MutableStateFlow<ManageTokensUM> = MutableStateFlow(getInitialState(params.userWalletId))
|
||||
val bottomSheetNavigation: SlotNavigation<ManageTokensBottomSheetConfig> = SlotNavigation()
|
||||
|
||||
|
|
|
|||
|
|
@ -40,16 +40,18 @@ import javax.inject.Inject
|
|||
@ModelScoped
|
||||
internal class OnboardingManageTokensModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val manageTokensListManager: ManageTokensListManager,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
|
||||
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
manageTokensListManagerFactory: ManageTokensListManager.Factory,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: OnboardingManageTokensComponent.Params = paramsContainer.require()
|
||||
private val manageTokensListManager = manageTokensListManagerFactory.create()
|
||||
|
||||
val state: MutableStateFlow<OnboardingManageTokensUM> = MutableStateFlow(getInitialState())
|
||||
val returnToParentComponentFlow = MutableSharedFlow<Unit>()
|
||||
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ import com.tangem.core.ui.components.BottomFade
|
|||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.TangemSwitch
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButton
|
||||
|
|
@ -41,8 +39,6 @@ import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
|||
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.list.InfiniteListHandler
|
||||
import com.tangem.core.ui.components.rows.ArrowRow
|
||||
import com.tangem.core.ui.components.rows.BlockchainRow
|
||||
|
|
@ -51,7 +47,6 @@ import com.tangem.core.ui.components.rows.ChainRowContainer
|
|||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
import com.tangem.core.ui.components.rows.model.ChainRowUM
|
||||
import com.tangem.core.ui.event.EventEffect
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
|
|
@ -66,14 +61,13 @@ import com.tangem.features.managetokens.component.ManageTokensSource
|
|||
import com.tangem.features.managetokens.component.preview.PreviewManageTokensComponent
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private const val CHEVRON_ROTATION_EXPANDED = 180f
|
||||
private const val CHEVRON_ROTATION_COLLAPSED = 0f
|
||||
private const val LOAD_ITEMS_BUFFER = 10
|
||||
internal const val LOAD_ITEMS_BUFFER = 10
|
||||
|
||||
@Composable
|
||||
internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -85,7 +79,9 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
|
|||
contentWindowInsets = WindowInsetsZero,
|
||||
topBar = {
|
||||
ManageTokensTopBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
topBar = state.topBar,
|
||||
search = state.search,
|
||||
)
|
||||
|
|
@ -116,31 +112,6 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM?, search: SearchBarUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.background(TangemTheme.colors.background.primary),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
if (topBar != null) {
|
||||
TangemTopAppBar(
|
||||
title = topBar.title.resolveReference(),
|
||||
startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick),
|
||||
endButton = when (topBar) {
|
||||
is ManageTokensTopBarUM.ManageContent -> topBar.endButton
|
||||
is ManageTokensTopBarUM.ReadContent -> null
|
||||
},
|
||||
)
|
||||
}
|
||||
SearchBar(
|
||||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens.spacing12)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
state = search,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SaveChangesButton(
|
||||
isVisible: Boolean,
|
||||
|
|
@ -263,7 +234,7 @@ internal fun Currencies(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchNothingFoundText(modifier: Modifier = Modifier) {
|
||||
fun SearchNothingFoundText(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
@ -277,7 +248,7 @@ private fun SearchNothingFoundText(modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ProgressIndicator(modifier: Modifier = Modifier) {
|
||||
fun ProgressIndicator(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.background(color = TangemTheme.colors.background.primary),
|
||||
contentAlignment = Alignment.Center,
|
||||
|
|
@ -287,7 +258,7 @@ private fun ProgressIndicator(modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingItem(modifier: Modifier = Modifier) {
|
||||
fun LoadingItem(modifier: Modifier = Modifier) {
|
||||
ChainRowContainer(
|
||||
modifier = modifier,
|
||||
icon = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.managetokens.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
import com.tangem.core.ui.components.fields.TangemSearchBarDefaults
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.managetokens.entity.managetokens.ManageTokensTopBarUM
|
||||
|
||||
@Composable
|
||||
internal fun ManageTokensTopBar(topBar: ManageTokensTopBarUM?, search: SearchBarUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (topBar != null) {
|
||||
TangemTopAppBar(
|
||||
title = topBar.title.resolveReference(),
|
||||
startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick),
|
||||
endButton = when (topBar) {
|
||||
is ManageTokensTopBarUM.ManageContent -> topBar.endButton
|
||||
is ManageTokensTopBarUM.ReadContent -> null
|
||||
},
|
||||
)
|
||||
}
|
||||
SearchBar(
|
||||
colors = TangemSearchBarDefaults.secondaryTextFieldColors,
|
||||
state = search,
|
||||
modifier = Modifier
|
||||
.padding(bottom = TangemTheme.dimens.spacing12)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,14 @@ package com.tangem.features.managetokens.utils.list
|
|||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.managetokens.*
|
||||
import com.tangem.domain.managetokens.model.*
|
||||
|
|
@ -16,6 +19,7 @@ import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
|
|||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.utils.ui.toggleExpanded
|
||||
import com.tangem.pagination.Batch
|
||||
import com.tangem.pagination.BatchAction
|
||||
import com.tangem.pagination.BatchListState
|
||||
|
|
@ -24,6 +28,9 @@ import com.tangem.utils.Provider
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -32,11 +39,9 @@ import kotlinx.coroutines.coroutineScope
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class ManageTokensListManager @Inject constructor(
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class ManageTokensListManager @AssistedInject constructor(
|
||||
private val getManagedTokensUseCase: GetManagedTokensUseCase,
|
||||
private val getDistinctManagedTokensUseCase: GetDistinctManagedCurrenciesUseCase,
|
||||
private val checkHasLinkedTokensUseCase: CheckHasLinkedTokensUseCase,
|
||||
|
|
@ -45,7 +50,8 @@ internal class ManageTokensListManager @Inject constructor(
|
|||
private val messageSender: UiMessageSender,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
clipboardManager: ClipboardManager,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
@Assisted private val onCurrencySelect: (ManagedCryptoCurrency.Token) -> Unit = {},
|
||||
) : ManageTokensUiActions {
|
||||
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
|
@ -67,7 +73,6 @@ internal class ManageTokensListManager @Inject constructor(
|
|||
actions = this,
|
||||
scopeProvider = Provider { scope },
|
||||
sourceProvider = Provider { source },
|
||||
clipboardManager = clipboardManager,
|
||||
)
|
||||
|
||||
val currenciesToAdd: StateFlow<ChangedCurrencies> = changedCurrenciesManager.currenciesToAdd.asStateFlow()
|
||||
|
|
@ -186,6 +191,14 @@ internal class ManageTokensListManager @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onTokenClick(currency: ManagedCryptoCurrency.Token) {
|
||||
if (source == ManageTokensSource.SEND_VIA_SWAP) {
|
||||
onCurrencySelect(currency)
|
||||
} else {
|
||||
toggleCurrencyNetworksVisibility(currency)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) {
|
||||
changedCurrenciesManager.addCurrency(currency, network)
|
||||
|
||||
|
|
@ -298,4 +311,168 @@ internal class ManageTokensListManager @Inject constructor(
|
|||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleCurrencyNetworksVisibility(currency: ManagedCryptoCurrency.Token) = scope.launch(
|
||||
dispatchers.default,
|
||||
) {
|
||||
state.update { batches ->
|
||||
val batchIndex = batches.batchIndexByCurrencyId(currency.id)
|
||||
val currencyBatch = batches.currencyBatches[batchIndex]
|
||||
val currencyIndex = currencyBatch.currencyIndexById(currency.id)
|
||||
|
||||
val uiBatch = batches.uiBatches[batchIndex]
|
||||
val updatedUiItem = uiBatch.data[currencyIndex].toggleExpanded(
|
||||
currency = currencyBatch.data[currencyIndex],
|
||||
isEditable = batches.canEditItems,
|
||||
onSelectCurrencyNetwork = { networkId, isSelected ->
|
||||
selectNetwork(currencyBatch.key, currency, networkId, isSelected)
|
||||
},
|
||||
onLongTap = ::copyContractAddress,
|
||||
)
|
||||
|
||||
batches.updateUiBatchesItem(
|
||||
indexToBatch = batchIndex to uiBatch,
|
||||
indexToItem = currencyIndex to updatedUiItem,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyContractAddress(source: ManagedCryptoCurrency.SourceNetwork) {
|
||||
if (source is ManagedCryptoCurrency.SourceNetwork.Default) {
|
||||
clipboardManager.setText(text = source.contractAddress, isSensitive = false)
|
||||
showSnackbarMessage(resourceReference(R.string.contract_address_copied_message))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSnackbarMessage(messageText: TextReference) {
|
||||
val message = SnackbarMessage(message = messageText)
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private fun selectNetwork(
|
||||
batchKey: Int,
|
||||
currency: ManagedCryptoCurrency,
|
||||
source: ManagedCryptoCurrency.SourceNetwork,
|
||||
isSelected: Boolean,
|
||||
) = scope.launch(dispatchers.default) {
|
||||
if (currency !is ManagedCryptoCurrency.Token) return@launch
|
||||
|
||||
if (isSelected) {
|
||||
val userWalletId = state.value.userWalletId
|
||||
val unsupportedState = userWalletId?.let { checkCurrencyUnsupportedState(it, source) }
|
||||
if (unsupportedState != null) {
|
||||
showUnsupportedWarning(unsupportedState)
|
||||
} else {
|
||||
addCurrency(batchKey, currency, source.network)
|
||||
}
|
||||
} else {
|
||||
if (checkNeedToShowRemoveNetworkWarning(currency, source.network)) {
|
||||
showRemoveNetworkWarning(
|
||||
currency = currency,
|
||||
network = source.network,
|
||||
isCoin = source is ManagedCryptoCurrency.SourceNetwork.Main,
|
||||
onConfirm = {
|
||||
removeCurrency(batchKey, currency, source.network)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
removeCurrency(batchKey, currency, source.network)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUnsupportedWarning(unsupportedState: CurrencyUnsupportedState) {
|
||||
val message = DialogMessage(
|
||||
title = resourceReference(R.string.common_warning),
|
||||
message = when (unsupportedState) {
|
||||
is CurrencyUnsupportedState.Token.NetworkTokensUnsupported -> resourceReference(
|
||||
id = R.string.alert_manage_tokens_unsupported_message,
|
||||
formatArgs = wrappedList(unsupportedState.networkName),
|
||||
)
|
||||
is CurrencyUnsupportedState.Token.UnsupportedCurve -> resourceReference(
|
||||
id = R.string.alert_manage_tokens_unsupported_curve_message,
|
||||
formatArgs = wrappedList(unsupportedState.networkName),
|
||||
)
|
||||
is CurrencyUnsupportedState.UnsupportedNetwork -> resourceReference(
|
||||
id = R.string.alert_manage_tokens_unsupported_curve_message,
|
||||
formatArgs = wrappedList(unsupportedState.networkName),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private suspend fun showRemoveNetworkWarning(
|
||||
currency: ManagedCryptoCurrency,
|
||||
network: Network,
|
||||
isCoin: Boolean,
|
||||
onConfirm: () -> Unit,
|
||||
) {
|
||||
val userWalletId = state.value.userWalletId
|
||||
val hasLinkedTokens = if (userWalletId == null || !isCoin) {
|
||||
false
|
||||
} else {
|
||||
checkHasLinkedTokens(userWalletId, network)
|
||||
}
|
||||
val canHideWithoutConfirming = source == ManageTokensSource.ONBOARDING
|
||||
|
||||
if (hasLinkedTokens) {
|
||||
showLinkedTokensWarning(currency, network)
|
||||
} else if (canHideWithoutConfirming) {
|
||||
onConfirm()
|
||||
} else {
|
||||
showHideTokenWarning(currency, onConfirm)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLinkedTokensWarning(currency: ManagedCryptoCurrency, network: Network) {
|
||||
val message = DialogMessage(
|
||||
title = resourceReference(
|
||||
id = R.string.token_details_unable_hide_alert_title,
|
||||
formatArgs = wrappedList(currency.name),
|
||||
),
|
||||
message = resourceReference(
|
||||
id = R.string.token_details_unable_hide_alert_message,
|
||||
formatArgs = wrappedList(
|
||||
currency.name,
|
||||
currency.symbol,
|
||||
network.name,
|
||||
),
|
||||
),
|
||||
)
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private fun showHideTokenWarning(currency: ManagedCryptoCurrency, onConfirm: () -> Unit) {
|
||||
val message = DialogMessage(
|
||||
title = resourceReference(
|
||||
id = R.string.token_details_hide_alert_title,
|
||||
formatArgs = wrappedList(currency.name),
|
||||
),
|
||||
message = resourceReference(R.string.token_details_hide_alert_message),
|
||||
firstActionBuilder = {
|
||||
EventMessageAction(
|
||||
title = resourceReference(R.string.token_details_hide_alert_hide),
|
||||
warning = true,
|
||||
onClick = onConfirm,
|
||||
)
|
||||
},
|
||||
secondActionBuilder = { cancelAction() },
|
||||
)
|
||||
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private fun Batch<Int, List<ManagedCryptoCurrency>>.currencyIndexById(id: ManagedCryptoCurrency.ID): Int {
|
||||
return data
|
||||
.indexOfFirst { it.id == id }
|
||||
.takeIf { it != -1 }
|
||||
?: error("Currency with currency '$id' not found in batch #$key")
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(onCurrencySelect: (ManagedCryptoCurrency.Token) -> Unit = {}): ManageTokensListManager
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
|
||||
internal interface ManageTokensUiActions {
|
||||
|
||||
fun onTokenClick(currency: ManagedCryptoCurrency.Token)
|
||||
|
||||
fun addCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network)
|
||||
|
||||
fun removeCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
package com.tangem.features.managetokens.utils.list
|
||||
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.managetokens.model.CurrencyUnsupportedState
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.features.managetokens.component.ManageTokensSource
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import com.tangem.features.managetokens.impl.R
|
||||
import com.tangem.features.managetokens.utils.mapper.toUiModel
|
||||
import com.tangem.features.managetokens.utils.ui.toggleExpanded
|
||||
import com.tangem.features.managetokens.utils.ui.update
|
||||
import com.tangem.pagination.Batch
|
||||
import com.tangem.utils.Provider
|
||||
|
|
@ -25,7 +20,10 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -36,7 +34,6 @@ internal class ManageTokensUiManager(
|
|||
private val scopeProvider: Provider<CoroutineScope>,
|
||||
private val sourceProvider: Provider<ManageTokensSource>,
|
||||
private val actions: ManageTokensUiActions,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
) {
|
||||
|
||||
private val scope: CoroutineScope
|
||||
|
|
@ -72,7 +69,7 @@ internal class ManageTokensUiManager(
|
|||
item.toUiModel(
|
||||
isEditable = canEditItems,
|
||||
onRemoveCustomCurrencyClick = ::removeCustomCurrency,
|
||||
onExpandNetworksClick = ::toggleCurrencyNetworksVisibility,
|
||||
onTokenClick = actions::onTokenClick,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -96,7 +93,7 @@ internal class ManageTokensUiManager(
|
|||
item.toUiModel(
|
||||
isEditable = canEditItems,
|
||||
onRemoveCustomCurrencyClick = ::removeCustomCurrency,
|
||||
onExpandNetworksClick = ::toggleCurrencyNetworksVisibility,
|
||||
onTokenClick = actions::onTokenClick,
|
||||
)
|
||||
} else {
|
||||
previousUiItem.update(item)
|
||||
|
|
@ -123,97 +120,6 @@ internal class ManageTokensUiManager(
|
|||
)
|
||||
}
|
||||
|
||||
private fun toggleCurrencyNetworksVisibility(currency: ManagedCryptoCurrency.Token) = scope.launch(
|
||||
dispatchers.default,
|
||||
) {
|
||||
state.update { batches ->
|
||||
val batchIndex = batches.batchIndexByCurrencyId(currency.id)
|
||||
val currencyBatch = batches.currencyBatches[batchIndex]
|
||||
val currencyIndex = currencyBatch.currencyIndexById(currency.id)
|
||||
|
||||
val uiBatch = batches.uiBatches[batchIndex]
|
||||
val updatedUiItem = uiBatch.data[currencyIndex].toggleExpanded(
|
||||
currency = currencyBatch.data[currencyIndex],
|
||||
isEditable = batches.canEditItems,
|
||||
onSelectCurrencyNetwork = { networkId, isSelected ->
|
||||
selectNetwork(currencyBatch.key, currency, networkId, isSelected)
|
||||
},
|
||||
onLongTap = ::copyContractAddress,
|
||||
)
|
||||
|
||||
batches.updateUiBatchesItem(
|
||||
indexToBatch = batchIndex to uiBatch,
|
||||
indexToItem = currencyIndex to updatedUiItem,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyContractAddress(source: ManagedCryptoCurrency.SourceNetwork) {
|
||||
if (source is ManagedCryptoCurrency.SourceNetwork.Default) {
|
||||
clipboardManager.setText(text = source.contractAddress, isSensitive = false)
|
||||
showSnackbarMessage(resourceReference(R.string.contract_address_copied_message))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSnackbarMessage(messageText: TextReference) {
|
||||
val message = SnackbarMessage(message = messageText)
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private fun selectNetwork(
|
||||
batchKey: Int,
|
||||
currency: ManagedCryptoCurrency,
|
||||
source: ManagedCryptoCurrency.SourceNetwork,
|
||||
isSelected: Boolean,
|
||||
) = scope.launch(dispatchers.default) {
|
||||
if (currency !is ManagedCryptoCurrency.Token) return@launch
|
||||
|
||||
if (isSelected) {
|
||||
val userWalletId = state.value.userWalletId
|
||||
val unsupportedState = userWalletId?.let { actions.checkCurrencyUnsupportedState(it, source) }
|
||||
if (unsupportedState != null) {
|
||||
showUnsupportedWarning(unsupportedState)
|
||||
} else {
|
||||
actions.addCurrency(batchKey, currency, source.network)
|
||||
}
|
||||
} else {
|
||||
if (actions.checkNeedToShowRemoveNetworkWarning(currency, source.network)) {
|
||||
showRemoveNetworkWarning(
|
||||
currency = currency,
|
||||
network = source.network,
|
||||
isCoin = source is ManagedCryptoCurrency.SourceNetwork.Main,
|
||||
onConfirm = {
|
||||
actions.removeCurrency(batchKey, currency, source.network)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
actions.removeCurrency(batchKey, currency, source.network)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUnsupportedWarning(unsupportedState: CurrencyUnsupportedState) {
|
||||
val message = DialogMessage(
|
||||
title = resourceReference(R.string.common_warning),
|
||||
message = when (unsupportedState) {
|
||||
is CurrencyUnsupportedState.Token.NetworkTokensUnsupported -> resourceReference(
|
||||
id = R.string.alert_manage_tokens_unsupported_message,
|
||||
formatArgs = wrappedList(unsupportedState.networkName),
|
||||
)
|
||||
is CurrencyUnsupportedState.Token.UnsupportedCurve -> resourceReference(
|
||||
id = R.string.alert_manage_tokens_unsupported_curve_message,
|
||||
formatArgs = wrappedList(unsupportedState.networkName),
|
||||
)
|
||||
is CurrencyUnsupportedState.UnsupportedNetwork -> resourceReference(
|
||||
id = R.string.alert_manage_tokens_unsupported_curve_message,
|
||||
formatArgs = wrappedList(unsupportedState.networkName),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private suspend fun showRemoveNetworkWarning(
|
||||
currency: ManagedCryptoCurrency,
|
||||
network: Network,
|
||||
|
|
@ -274,11 +180,4 @@ internal class ManageTokensUiManager(
|
|||
|
||||
messageSender.send(message)
|
||||
}
|
||||
|
||||
private fun Batch<Int, List<ManagedCryptoCurrency>>.currencyIndexById(id: ManagedCryptoCurrency.ID): Int {
|
||||
return data
|
||||
.indexOfFirst { it.id == id }
|
||||
.takeIf { it != -1 }
|
||||
?: error("Currency with currency '$id' not found in batch #$key")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.managetokens.utils.list
|
||||
|
||||
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal fun getLoadingItems(): ImmutableList<CurrencyItemUM> {
|
||||
return List(size = 10) { index ->
|
||||
CurrencyItemUM.Loading(index)
|
||||
}.toPersistentList()
|
||||
}
|
||||
|
|
@ -11,11 +11,11 @@ import com.tangem.features.managetokens.utils.ui.getIconRes
|
|||
|
||||
internal fun ManagedCryptoCurrency.toUiModel(
|
||||
isEditable: Boolean,
|
||||
onExpandNetworksClick: (ManagedCryptoCurrency.Token) -> Unit,
|
||||
onTokenClick: (ManagedCryptoCurrency.Token) -> Unit,
|
||||
onRemoveCustomCurrencyClick: (ManagedCryptoCurrency.Custom) -> Unit,
|
||||
): CurrencyItemUM = when (this) {
|
||||
is ManagedCryptoCurrency.Custom -> toUiModel(onRemoveCustomCurrencyClick)
|
||||
is ManagedCryptoCurrency.Token -> toUiModel(isEditable, onExpandNetworksClick)
|
||||
is ManagedCryptoCurrency.Token -> toUiModel(isEditable, onTokenClick)
|
||||
}
|
||||
|
||||
private fun ManagedCryptoCurrency.Custom.toUiModel(
|
||||
|
|
@ -53,7 +53,7 @@ private fun ManagedCryptoCurrency.Custom.toUiModel(
|
|||
|
||||
private fun ManagedCryptoCurrency.Token.toUiModel(
|
||||
isEditable: Boolean,
|
||||
onExpandNetworksClick: (ManagedCryptoCurrency.Token) -> Unit,
|
||||
onTokenClick: (ManagedCryptoCurrency.Token) -> Unit,
|
||||
): CurrencyItemUM {
|
||||
val background = TangemColorPalette.Black
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ private fun ManagedCryptoCurrency.Token.toUiModel(
|
|||
),
|
||||
networks = NetworksUM.Collapsed,
|
||||
onExpandClick = {
|
||||
onExpandNetworksClick(this)
|
||||
onTokenClick(this)
|
||||
},
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue