diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efa92ac9ff..4fdc8ffde8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -372,6 +372,8 @@ dependencies { implementation(deps.coil.svg) implementation(deps.amplitude) implementation(deps.appsflyer) + implementation(deps.appsflyer.oaid) + implementation("com.android.installreferrer:installreferrer:2.2") implementation(deps.spongecastle.core) implementation(deps.lottie) implementation(deps.compose.accompanist.appCompatTheme) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt index 14a35c89cd..26675e0fef 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/handlers/appsflyer/AppsFlyerAnalyticsHandler.kt @@ -26,8 +26,9 @@ class AppsFlyerAnalyticsHandler( } is AppsFlyerIncludedEvent -> { Timber.tag("AppsFlyer").i("Sending event to AppsFlyer: ${event.id} with params: ${event.params}") + val replacedEvent = event.appsFlyerReplacedEvent ?: event.event client.logEvent( - event = AnalyticsEvent(category = event.category, event = event.appsFlyerReplacedEvent).id, + event = AnalyticsEvent(category = event.category, event = replacedEvent).id, params = event.params, ) } diff --git a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt index fb02cff093..4dc9cefd1a 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt @@ -16,6 +16,7 @@ import com.tangem.sdk.storage.AndroidSecureStorageV2 import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.withContext +import java.util.UUID import javax.inject.Inject import javax.inject.Singleton @@ -71,12 +72,21 @@ internal class DefaultTangemPayStorage @Inject constructor( ) } - override suspend fun getAuthTokens(customerWalletAddress: String): TangemPayAuthTokens? = - withContext(dispatcherProvider.io) { - secureStorage.get(createAuthTokensKey(customerWalletAddress)) - ?.decodeToString(throwOnInvalidSequence = true) - ?.let(tokensAdapter::fromJson) + override suspend fun getAuthTokens(customerWalletAddress: String): TangemPayAuthTokens? { + val authTokens = secureStorage.get(createAuthTokensKey(customerWalletAddress)) + ?.decodeToString(throwOnInvalidSequence = true) + ?.let(tokensAdapter::fromJson) + + return authTokens?.let { tokens -> + if (tokens.idempotencyKey == null) { + val newAuthTokens = tokens.copy(idempotencyKey = UUID.randomUUID().toString()) + storeAuthTokens(customerWalletAddress, newAuthTokens) + newAuthTokens + } else { + tokens + } } + } override suspend fun clearAuthTokens(customerWalletAddress: String) { secureStorage.delete(createAuthTokensKey(customerWalletAddress)) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index f45812a062..f182a2555b 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -190,6 +190,7 @@ internal class ChildFactory @Inject constructor( context = context, params = WalletBackupComponent.Params( userWalletId = route.userWalletId, + isColdWalletOptionShown = route.isColdWalletOptionShown, ), componentFactory = walletBackupComponentFactory, ) diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index f70bb78a58..2be124d092 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -232,7 +232,8 @@ sealed class AppRoute(val path: String) : Route { @Serializable data class WalletBackup( val userWalletId: UserWalletId, - ) : AppRoute(path = "/wallet_backup/${userWalletId.stringValue}") + val isColdWalletOptionShown: Boolean, + ) : AppRoute(path = "/wallet_backup/${userWalletId.stringValue}/$isColdWalletOptionShown") @Serializable data class WalletHardwareBackup( diff --git a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt index 1941c34ff9..dde935dbec 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt @@ -36,6 +36,7 @@ import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.SendScreenTestTags +import com.tangem.core.ui.utils.GlobalMultipleClickPreventer import com.tangem.core.ui.utils.singleEvent @Composable @@ -112,10 +113,12 @@ fun NavigationPrimaryButton(primaryButton: NavigationButton?, modifier: Modifier text = button.textReference.resolveReference(), enabled = button.isEnabled, onClick = { - if (button.isHapticClick) { - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + GlobalMultipleClickPreventer.processEvent { + if (button.isHapticClick) { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + } + button.onClick() } - button.onClick() }, showProgress = button.shouldShowProgress, colors = color, diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AppsFlyerOnlyEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AppsFlyerOnlyEvent.kt index a265fcfd9f..757f31ff55 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AppsFlyerOnlyEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AppsFlyerOnlyEvent.kt @@ -11,5 +11,6 @@ interface AppsFlyerOnlyEvent * Events implementing this interface will be sent to AppsFlyer along with other analytics handlers */ interface AppsFlyerIncludedEvent { - val appsFlyerReplacedEvent: String + val appsFlyerReplacedEvent: String? + get() = null } \ No newline at end of file diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt index c59513d2fa..7a67705605 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/Basic.kt @@ -93,7 +93,7 @@ sealed class Basic( } this["Memo"] = memoType.name }, - ) { + ), AppsFlyerIncludedEvent { enum class MemoType { Empty, Full, Null } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt index c1cad4c1a7..e449459d0e 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/OnboardingAnalyticsEvent.kt @@ -3,7 +3,6 @@ package com.tangem.core.analytics.models.event import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AppsFlyerIncludedEvent -import com.tangem.core.analytics.models.AppsFlyerOnlyEvent sealed class OnboardingAnalyticsEvent( category: String, @@ -16,8 +15,6 @@ sealed class OnboardingAnalyticsEvent( params: Map = mapOf(), ) : OnboardingAnalyticsEvent(category = "Onboarding", event = event, params = params) { - class AppsFlyerOnlyEntryScreenView : Onboarding(event = "wallet_entry_screen_view"), AppsFlyerOnlyEvent - class Started( source: String, ) : Onboarding( @@ -68,12 +65,7 @@ sealed class OnboardingAnalyticsEvent( put("Seed Phrase Length", seedPhraseLength.toString()) } }, - ), AppsFlyerIncludedEvent { - override val appsFlyerReplacedEvent = when (creationType) { - WalletCreationType.NewSeed -> "wallet_created_successfully" - WalletCreationType.SeedImport -> "wallet_imported" - } - } + ), AppsFlyerIncludedEvent sealed class WalletCreationType(val value: String) { data object NewSeed : WalletCreationType(value = "New Seed") @@ -93,7 +85,7 @@ sealed class OnboardingAnalyticsEvent( params = mapOf( AnalyticsParam.SOURCE to source, ), - ) + ), AppsFlyerIncludedEvent class ButtonImportWallet : SeedPhrase("Button - Import Wallet") class ImportSeedPhraseScreenOpened : SeedPhrase("Import Seed Phrase Screen Opened") diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 59a153de56..72e92884a0 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -35,6 +35,10 @@ "name": "HOT_WALLET_ENABLED", "version": "5.32.0" }, + { + "name": "HOT_WALLET_CREATION_RESTRICTION_ENABLED", + "version": "5.32.0" + }, { "name": "TANGEM_PAY_ENABLED", "version": "5.31.0" diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayAuthApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayAuthApi.kt index fbf512024c..7cf839ce31 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayAuthApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayAuthApi.kt @@ -7,6 +7,7 @@ import com.tangem.datasource.api.pay.models.request.RefreshCustomerWalletAccessT import com.tangem.datasource.api.pay.models.response.TangemPayGenerateNonceResponse import com.tangem.datasource.api.pay.models.response.TangemPayGetTokensResponse import retrofit2.http.Body +import retrofit2.http.Header import retrofit2.http.POST interface TangemPayAuthApi { @@ -23,6 +24,7 @@ interface TangemPayAuthApi { @POST("auth/token/refresh") suspend fun refreshCustomerWalletAccessToken( + @Header("Idempotency-Key") idempotencyKey: String, @Body request: RefreshCustomerWalletAccessTokenRequest, ): ApiResponse } \ No newline at end of file diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 70a5296c78..b15f9aba6a 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -327,6 +327,7 @@ Ahora OK Abrir en el navegador + o Tarjeta principal Anillo primario Frase de contraseña @@ -437,6 +438,7 @@ Este mecanismo protege contra los ataques de proximidad a una tarjeta o anillo. Aplicará un retardo entre la recepción y la ejecución de un comando. Contraseña Antes de ejecutar un comando que cambie el estado de la tarjeta, deberá ingresar una contraseña. + Actualizar a billetera de hardware NFT Programa de referidos Gire la pantalla de su dispositivo hacia abajo para ocultar y mostrar rápidamente los saldos @@ -450,6 +452,7 @@ Firmado Enviar comentarios Detalles + Puedes tener solo una billetera móvil a la vez. Puede actualizarse a una billetera fría Tangem o usarse junto con una nueva billetera fría. Compruebe su conexión a internet o cambia a una red diferente Condiciones de uso Dirección por defecto @@ -569,8 +572,8 @@ Para continuar, conceda a los contratos inteligentes de %1s permiso para utilizar su %2s Dar autorización Ilimitado - Su copia de seguridad se crea utilizando 2 o 3 tarjetas Tangem. Guárdalas en lugares seguros y separados para protegerlas de pérdidas o daños. - Copia de seguridad con varias tarjetas + Se generarán nuevas direcciones dentro de su billetera física, listas para su uso y totalmente seguras. + Nuevas direcciones Agregar billetera Tangem Su clave privada se genera directamente dentro de la tarjeta Tangem y nunca sale de ella. Generación de claves @@ -611,11 +614,15 @@ Frase de recuperación Para proteger su billetera con un código de acceso, complete el proceso de copia de seguridad. Para actualizar a una billetera de hardware, complete el proceso de copia de seguridad. + Actualice su billetera móvil a una billetera de hardware Tangem para obtener la máxima seguridad. Importe su billetera o transfiera fondos a una nueva. + Actualizar a billetera fría Sus claves privadas están encriptadas de forma segura y almacenadas en su teléfono Las claves privadas permanecen en su dispositivo Cree o restaure su billetera con una frase de recuperación. Copia de seguridad de la frase semilla Crear una billetera móvil + Transfiera su billetera móvil a una tarjeta o anillo Tangem en cualquier momento, de forma segura. + Actualización a billetera de hardware Importar billetera existente Esta frase de recuperación ya ha sido importada Billetera móvil @@ -633,14 +640,14 @@ Entiendo que si no he hecho una copia de seguridad de mi billetera antes de eliminarla, perderé el acceso a ella. Entiendo que quitar mi billetera no la borra, solo la elimina de mi dispositivo. Actualizar - No se requiere frase semilla. Su tarjeta o anillo Tangem se convierte en su copia de seguridad. - Copia de seguridad con Tangem + Todas sus direcciones y saldos permanecen completamente accesibles al actualizar su billetera móvil a una tarjeta o anillo Tangem. + Mismas direcciones No se puede actualizar. Ya existe una billetera en este dispositivo. Elija otro dispositivo. Este no se puede usar para la actualización. Se produjo un error durante la operación. Sus fondos permanecen seguros y totalmente accesibles durante el proceso Acceso a los fondos - Los datos de su billetera se borrarán de la aplicación y se almacenarán en su billetera de hardware. + Después de la actualización, su billetera móvil se eliminará de la aplicación y se almacenará en su billetera de hardware; su frase de recuperación permanecerá bajo su control. Seguridad general Las claves privadas se transferirán de la aplicación a su billetera de hardware Tangem Migración de claves @@ -1242,6 +1249,11 @@ Escanee la tarjeta/anillo que quiere configurar Olvidar la billetera Esto eliminará la wallet de la aplicación. La wallet en sí puede\nañadirse de nuevo. + Fácil de usar + Mantenga sus criptos seguras y sin conexión. Tan delgadas como una tarjeta de crédito, más seguras que una bóveda bancaria. + Sin seed phrase + La mejor billetera de hardware de su clase + Billetera Fría Tangem Nombre Haga que su token trabaje para Ud. Una tarifa de red es un pequeño pago necesario para procesar y confirmar su transacción en la blockchain. @@ -1613,6 +1625,7 @@ Hemos encontrado un error. Código de error: %s. Póngase en contacto con nuestro servicio de soporte. Use %s o escanee una tarjeta/anillo para tener acceso a su billetera Error de conexión: Esta dApp utiliza la versión 1.0 de Wallet Connect, que no es compatible. Asegúrese de que la dApp sea compatible con la versión 2.0 de Wallet Connect para conectarse correctamente. + Actualización a billetera de hardware Manténgase actualizado con las últimas funciones y noticias Alertas en tiempo real de transacciones, intercambios y actualizaciones críticas. Alertas de transacciones @@ -1982,6 +1995,7 @@ Entrega rápida Empiece con un solo toque Fácil y seguro + Sin seed phrase Fácil de usar Cree una billetera de hardware con Tangem. Delgada como una tarjeta bancaria, segura como una bóveda. Crear o importar una billetera de software diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index a59573d0ab..565f5cf8f8 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -322,6 +322,7 @@ わかりました ブラウザで開く + または プライマリーカード プライマリーリング パスフレーズ @@ -444,7 +445,7 @@ 署名済み フィードバックを送信 詳細 - 同時に利用できるのは、モバイルウォレットまたはハードウェアウォレットのいずれか一方のみです。Tangemのコールドウォレットを利用するには、現在のウォレットをアップグレードするか、アプリから削除してください。 + モバイルウォレットは同時に1つのみ利用できます。Tangemコールドウォレットにアップグレードすることも、新しいコールドウォレットと併用することも可能です。 インターネット接続を確認するか、別のネットワークに切り替えてください。 利用規約 デフォルトアドレス @@ -555,7 +556,7 @@ 取引を送信できません コインの説明エラー 残高不足 - ガス代不要の取引手数料 + 取引手数料 エラーが発生しました エラーが発生しました。コード: %s 。 メモが必要 @@ -567,10 +568,10 @@ 続行するには、%1sスマートコントラクトに%2sを使用する権限を付与してください 許可を与える 無制限 - バックアップは2〜3枚のTangemカードを使用して作成されます。紛失や破損に備え、カードはそれぞれ安全な別の場所に保管してください。シードフレーズは不要です。 - 複数のカードでバックアップ + ハードウェアウォレット内に新しいアドレスが自動生成され、安全にすぐ利用できる状態になります。 + 新しいアドレス Tangemウォレットを追加 - あなたの秘密鍵はTangemカード内部で直接生成され、決してカードの外に出ることはありません。 + 秘密鍵はTangemカード内部で直接生成され、外部に出ることは一切ありません。 鍵生成 すべての暗号処理は、複製や物理的な改ざんに対する認証を受けたセキュアチップ内で行われます。 ハードウェアレベルのセキュリティ @@ -605,15 +606,20 @@ まずバックアップを完了する 未完了 その他の方法 + リカバリーフレーズは安全な場所に保管し、他人に知られないようにしてください。あわせて、資産をさらに強力に保護するためにアクセスコードを設定してください。 資金を保護するため、リカバリーフレーズは安全な場所に保管し、他人に知られないようにしてください。 リカバリーフレーズ アクセスコードでウォレットを保護するには、バックアップの手続きを完了してください。 ハードウェアウォレットにアップグレードするには、バックアップの手続きを完了してください。 + 業界最高レベルのセキュリティを実現するため、モバイルウォレットをTangemハードウェアウォレットにアップグレードしましょう。既存のウォレットをインポートするか、新しいウォレットへ資産を移行できます。 + コールドウォレットにアップグレード 秘密鍵は安全に暗号化され、スマートフォン上に保存されています 秘密鍵はデバイス上に保持されます リカバリーフレーズを使ってウォレットを作成または復元してください。 シードフレーズのバックアップ モバイルウォレットを作成する + モバイルウォレットは、いつでも安全にTangemカードまたはリングへ移行できます。 + ハードウェアウォレットにアップグレード 既存のウォレットをインポートする このリカバリーフレーズはすでにインポートされています。 モバイルウォレット @@ -631,14 +637,14 @@ ウォレットを削除する前にバックアップを行っていない場合、ウォレットへのアクセスを失うことを理解しています。 ウォレットを削除しても中身自体が消えるわけではなく、このデバイスから表示が消えるだけであることを理解しています。 アップグレード - シードフレーズは不要です。Tangemカードまたはリングが安全なバックアップとなります。 - Tangemでバックアップ + モバイルウォレットをTangemカードまたはリングにアップグレードしても、すべてのアドレスと残高は引き続き完全に利用できます。 + 同じアドレス アップグレードできません。このデバイスにはすでにウォレットが存在します。 別のデバイスを選択してください。このデバイスはアップグレードに使用できません。 操作中にエラーが発生しました。 処理中も、資金は安全に保たれ、常にアクセス可能です。 資金へのアクセス - ウォレット情報はアプリから削除され、ハードウェアウォレットに保存されます + アップグレード後、モバイルウォレットはアプリから削除され、ハードウェアウォレットに保存されます。リカバリーフレーズは引き続きご自身で管理してください。 セキュリティ全般 秘密鍵はアプリからTangemハードウェアウォレットへ移行されます キーの移行 @@ -1414,6 +1420,7 @@ 残高不足 許可を与える スワップ + スワップ中… 受け取る トークンを選択 利用不可 @@ -1540,6 +1547,8 @@ Tangem Payは現在一時的に利用できません。 Tangem Pay 下のボタンをクリックしてアクセスを復元してください + 現在引き出せるのは、保留中の取引分を差し引いたオンチェーンアドレス上のUSDC(Polygon)のみです。以前に返金や購入のキャンセルを行った場合、暗号資産アカウントの残高は2営業日以内に更新されます。 + ご注意ください PINコード これは私のウォレットです 残高非表示 @@ -1935,6 +1944,7 @@ 迅速な配送 ワンタップで開始 シームレスで安全 + シードフレーズ不要 シンプルな操作 Tangemでハードウェアウォレットを作成しよう。キャッシュカードのようにスリムで、金庫のように安全。 ソフトウェアウォレットを作成またはインポート diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index f54c4a89be..ef16028c2d 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -334,10 +334,12 @@ Нет Нет адреса Не добавлено + Не доступно Не сейчас Сейчас OK Открыть в браузере + или Основная карта Основное кольцо Парольная фраза @@ -450,6 +452,7 @@ Этот механизм защищает карту или кольцо от бесконтактных атак. Между сканированием и выполнением команды будет добавлена задержка. Пароль Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + Апгрейд до аппаратного кошелька NFT Реферальная программа Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы @@ -463,6 +466,7 @@ Подписано Отправить отзыв Подробности + Одновременно у вас может быть только один мобильный кошелек. Его можно апгрейдить до Tangem cold wallet или использовать вместе с новым холодным кошельком. Проверьте подключение с интернетом или переключитесь на другую сеть Условия использования Основной адрес @@ -569,6 +573,8 @@ Обращение в поддержку Tangem Не могу отправить транзакцию Ошибка в описании монеты + Недостаточно средств + Комиссия за транзакцию Произошла ошибка Произошла ошибка. Код: %s. Требуется memo @@ -580,10 +586,10 @@ Чтобы продолжить, вам нужно разрешить смарт-контракту %1s использовать ваш %2s Дать разрешение Безлимитно - Резервная копия создаётся на 2–3 картах Tangem. Храните их отдельно для безопасности — seed-фраза не нужна. - Бэкап на несколько карт + Новые адреса будут сгенерированы прямо в вашем аппаратном кошельке, готовы к использованию и полностью защищены. + Новые адреса Добавить кошелек Tangem - Ваш приватный ключ генерируется на карте Tangem и никогда не покидает её. + Ваш приватный ключ будет сгенерирован на карте Tangem и никогда не покинет её. Генерация ключа Операции с криптографией выполняются внутри защищённого чипа, устойчивого к клонированию и физическому взлому. Аппаратная безопасность @@ -618,15 +624,20 @@ Сначала завершите создание резервной копии Не завершено Другие способы + Сохраните фразу восстановления в надежном месте и держите её в секрете, чтобы защитить свои средства, а также установите код доступа для дополнительной безопасности. Сохраните фразу восстановления в безопасном месте и держите её в секрете. Фраза восстановления Чтобы защитить ваш кошелёк с помощью кода доступа, сначала завершите резервное копирование. Чтобы улучшить кошелёк до аппаратного, сначала создайте резервную копию. + Перенесите свой мобильный кошелек на аппаратный кошелек Tangem для максимальной безопасности. Импортируйте существующий кошелек или переведите средства на новый. + Апгрейд до холодного кошелька Ваши приватные ключи надёжно зашифрованы и хранятся на вашем телефоне Ключи хранятся в приложении Создайте или импортируйте кошелёк с помощью вашей фразы восстановления. Резервная копия Создать мобильный кошелек + Перенесите свой мобильный кошелек на Tangem-карту или кольцо в любое время. + Апгрейд до аппаратного кошелька Импортировать существующий Эта фраза восстановления уже была импортирована Мобильный кошелек @@ -644,14 +655,14 @@ Я понимаю, что если я не создал резервную копию кошелька перед его удалением, я могу потерять к нему доступ. Я понимаю, что удаление моего кошелька не стирает его — оно просто удаляет его с моего устройства. Апгрейд - Фраза восстановления больше не нужна — ваша карта или кольцо Tangem становятся вашей надёжной резервной копией. - Резервное копирование + Все ваши адреса и балансы остаются полностью доступными при апгрейде мобильного кошелька на Tangem-карту или кольцо. + Те же адреса Это устройство не может быть использовано для апгрейда, оно уже содержит другой кошелек. Выберите другое устройство. Это нельзя использовать для обновления. Во время операции произошла ошибка. Ваши средства остаются в безопасности и полностью доступны в процессе. Доступ к средствам - Данные вашего кошелька будут удалены из приложения и сохранены на вашем устройстве Tangem. + После апгрейда ваш мобильный кошелек будет удалён из приложения и сохранён на аппаратном кошельке; фраза восстановления остаётся у вас. Общая безопасность Приватные ключи будут перемещены из приложения в вашу Tangem карту или кольцо Миграция ключей @@ -1277,6 +1288,11 @@ Подготовьтесь к сканированию кольца или карты, которую вы хотите настроить. Забыть кошелек Это приведет к удалению кошелька из приложения. Сам кошелек можно добавить снова. + Прост в использовании + Хранит вашу криптовалюту в безопасности и офлайн. Тонкий, как кредитная карта, надежнее банковского сейфа. + Без seed-фразы + Лучший в своём классе аппаратный кошелек + Холодный кошелек Tangem Имя Заставьте ваш токен работать Сетевая комиссия — это небольшая плата за обработку и подтверждение вашей транзакции в блокчейне. @@ -1442,6 +1458,7 @@ Интуитивный обмен в пару касаний — без сложностей и ожидания Проще простого Обмен через провайдера + Ваши средства В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя. В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %1$s от биржи обратно на адрес пользователя \n\nПроскальзывание провайдера составляет до %2$s В сумму включена комиссия провайдера сервиса. @@ -1456,6 +1473,7 @@ Недостаточно средств Дать разрешение Обменять + Обмен… Вы получите Выберите токен не доступен @@ -1646,6 +1664,7 @@ Произошла ошибка. Код ошибки: %s. Попробуйте, пожалуйста, снова. Если проблема будет продолжать возникать — обратитесь в службу поддержки. Используйте %s или отсканируйте карту/кольцо, чтобы получить доступ к своему кошельку Соединение не удалось: это dApp использует Wallet Connect версии 1.0, которая не поддерживается. Убедитесь, что dApp поддерживает Wallet Connect версии 2.0 для успешного подключения. + Апгрейд до аппаратного кошелька Будьте в курсе новых функций и новостей Мгновенные уведомления о транзакциях, обменах и важных обновлениях. Уведомления о транзакциях @@ -1957,6 +1976,7 @@ Быстрая доставка Начните в один клик Просто и надёжно + Без seed-фразы Интуитивно понятный Создайте аппаратный кошелёк с Tangem. Тонкий, как банковская карта, безопасный, как банковский сейф. Создать или импортировать мобильный кошелёк diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 162f432ac1..f7732464c2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -328,6 +328,7 @@ Now OK Open in Browser + or Primary card Primary ring Passphrase @@ -452,7 +453,7 @@ Signed Send feedback Details - You can have either one mobile wallet or hardware wallet at the same time. Upgrade you wallet or delete it from the app to start using a Tangem cold wallet. + You can have only one mobile wallet at a time. It can be upgraded to a Tangem cold wallet or used alongside a new cold wallet. Check your internet connection or switch to a different network Terms of service Default Address @@ -563,7 +564,7 @@ Can\'t send a transaction Coin description error Not enough funds - Gasless transaction fee + Transaction fee An error occurred An error occurred. Code: %s. Requires memo @@ -575,10 +576,10 @@ To continue, grant %1s smart contracts permission to use your %2s Give Permission Unlimited - A backup is created using 2–3 Tangem cards. Store them separately in secure locations to protect against loss or damage. No seed phrase needed. - Backup With Multiple Cards + New addresses will be generated for you inside your hardware wallet, ready for use and fully secure. + New Addresses Add Tangem Wallet - Your private key is generated directly inside the Tangem card and never leaves it. + Your private key will be generated directly inside the Tangem card and will never leave it. Key Generation All cryptographic operations happen inside the secure chip, certified against cloning and physical tampering. Hardware-Level Security @@ -613,15 +614,20 @@ Finalize backup first Incomplete Other methods + Save your recovery phrase in a secure place and keep it private to protect your funds, and set up an access code for additional security. Save your recovery phrase in a secure place and keep it private to protect your funds. Recovery phrase To secure your wallet with an access code, complete the backup process. To upgrade to a hardware wallet, complete the backup process. + Upgrade your mobile wallet to a Tangem hardware wallet for the highest security. Import your wallet or transfer funds to a new one. + Upgrade to cold wallet Your private keys are securely encrypted and stored on your phone Private keys stay on your device Create or restore your wallet with a recovery phrase. Seed phrase backup Create Mobile Wallet + Transfer your mobile wallet to a Tangem card or ring anytime, securely. + Upgrade to hardware wallet Import existing wallet This recovery phrase has already been imported Mobile Wallet @@ -639,14 +645,14 @@ I understand that if I haven\'t backed up my wallet before removing it, I will lose access to it. I understand that removing my wallet does not delete it, only removes it from my device. Upgrade - Seed phrase not required. Your Tangem card or ring becomes your secure backup. - Backup With Tangem + All your addresses and balances stay fully accessible when upgrading your mobile wallet to a Tangem card or ring. + Same Addresses Can\'t upgrade. A wallet already exists on this device. Pick another device. This one can\'t be used for the upgrade. An error occurred during the operation. Your funds remain safe and fully accessible during the process Access to funds - Your wallet information will be erased from the app and stored on your hardware wallet + After upgrading, your mobile wallet will be removed from the app and stored on your hardware wallet; your recovery phrase stays with you. General security Private keys will be moved from the app to your Tangem hardware wallet Key migration @@ -1562,6 +1568,8 @@ Tangem Pay is temporarily unreachable Tangem Pay Click the button below to restore access + You can only withdraw USDC (Polygon) currently available on your on‑chain address balance, taking into account pending transactions. If you previously made a refund or cancelled a purchase, your crypto account balance will update within 2 business days. + Please note Your PIN code This is my wallet Balances hidden @@ -1633,7 +1641,7 @@ We\'ve encountered an error. Error code: %s. Please contact our support. Use %s or scan a card/ring to have access to your wallet Connection failed: This dApp uses Wallet Connect version 1.0, which is not supported. Please ensure the dApp supports Wallet Connect version 2.0 to connect successfully. - Upgrade to a hardware wallet + Upgrade to hardware wallet Stay up to date with the latest features and news Real-time alerts for transactions, exchanges, and critical updates. Transaction Alerts @@ -2004,6 +2012,7 @@ Fast delivery Start in one tap Seamless and secure + No seed phrase Simple to use Create a hardware wallet with Tangem. Slim as a bank card, secure as a bank vault. Create or import a software wallet diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockItem.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockItem.kt index 66cf54df35..be7f8f5b2c 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockItem.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockItem.kt @@ -27,16 +27,18 @@ fun BlockItem(model: BlockUM, modifier: Modifier = Modifier) { verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12, Alignment.Start), ) { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = model.iconRes), - tint = when (model.accentType) { - BlockUM.AccentType.NONE -> TangemTheme.colors.icon.secondary - BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent - BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning - }, - contentDescription = null, - ) + if (model.iconRes != null) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = model.iconRes), + tint = when (model.accentType) { + BlockUM.AccentType.NONE -> TangemTheme.colors.icon.secondary + BlockUM.AccentType.ACCENT -> TangemTheme.colors.text.accent + BlockUM.AccentType.WARNING -> TangemTheme.colors.text.warning + }, + contentDescription = null, + ) + } Text( modifier = Modifier.weight(1f), diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/block/model/BlockUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/model/BlockUM.kt index 4988f72eff..f0079ad959 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/block/model/BlockUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/model/BlockUM.kt @@ -7,7 +7,7 @@ import javax.annotation.concurrent.Immutable data class BlockUM( val text: TextReference, - @DrawableRes val iconRes: Int, + @DrawableRes val iconRes: Int?, val onClick: () -> Unit, val accentType: AccentType = AccentType.NONE, val endContent: EndContent = EndContent.None, diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/MultipleClickPreventer.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/MultipleClickPreventer.kt index 767e1ada4d..b6342687d3 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/utils/MultipleClickPreventer.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/utils/MultipleClickPreventer.kt @@ -15,6 +15,18 @@ interface MultipleClickPreventer { } } +/** + * A global instance of [MultipleClickPreventer] + * Use for preventing multiple clicks in the entire app. + * For example, when we must ensure correct data is passed while editing values and navigating to next screen. + * + * @example + * GlobalMultipleClickPreventer.processEvent { + * // Handle click event + * } + */ +val GlobalMultipleClickPreventer = MultipleClickPreventer.get() + private class DefaultMultipleClickPreventer : MultipleClickPreventer { private val now: Long get() = SystemClock.elapsedRealtime() private var lastEventTimeMs: Long = 0 diff --git a/core/ui/src/main/res/drawable/ic_backup_repeat_24.xml b/core/ui/src/main/res/drawable/ic_backup_repeat_24.xml new file mode 100644 index 0000000000..3579504b2c --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_backup_repeat_24.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_double_star_24.xml b/core/ui/src/main/res/drawable/ic_double_star_24.xml new file mode 100644 index 0000000000..78ca302987 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_double_star_24.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_mobile_security_2_24.xml b/core/ui/src/main/res/drawable/ic_mobile_security_2_24.xml new file mode 100644 index 0000000000..20f8ba11fa --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_mobile_security_2_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/core/ui/src/main/res/drawable/ic_seed_phrase_24.xml b/core/ui/src/main/res/drawable/ic_seed_phrase_24.xml new file mode 100644 index 0000000000..7ec0b491f4 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_seed_phrase_24.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_smartphone_24.xml b/core/ui/src/main/res/drawable/ic_smartphone_24.xml new file mode 100644 index 0000000000..eb4558d24b --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_smartphone_24.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_tangem_card_24.xml b/core/ui/src/main/res/drawable/ic_tangem_card_24.xml new file mode 100644 index 0000000000..2f4eabe10a --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_tangem_card_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt index e85c5c6f64..3e83c4ff0d 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import timber.log.Timber +import java.util.UUID import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import javax.inject.Singleton @@ -95,7 +96,7 @@ internal class TangemPayRequestPerformer @Inject constructor( if (accessExpiresAt.isAfter(now)) { tokens.right() } else if (accessExpiresAt.isBefore(now) && refreshExpiresAt.isAfter(now)) { - refreshAuthTokens(userWalletId = userWalletId, refreshToken = tokens.refreshToken) + refreshAuthTokens(userWalletId = userWalletId, authTokens = tokens) } else { VisaApiError.RefreshTokenExpired.left() } @@ -104,13 +105,15 @@ internal class TangemPayRequestPerformer @Inject constructor( private suspend fun refreshAuthTokens( userWalletId: UserWalletId, - refreshToken: String, + authTokens: TangemPayAuthTokens, ): Either { val customerWalletAddress = getCustomerWalletAddress(userWalletId) + val idempotencyKey = requireNotNull(authTokens.idempotencyKey) { "Idempotency key is null" } val apiResponse = tangemPayAuthApi.refreshCustomerWalletAccessToken( + idempotencyKey = idempotencyKey, request = RefreshCustomerWalletAccessTokenRequest( authType = "customer_wallet", - refreshToken = refreshToken, + refreshToken = authTokens.refreshToken, ), ) val responseEither = when (apiResponse) { @@ -123,6 +126,7 @@ internal class TangemPayRequestPerformer @Inject constructor( expiresAt = response.expiresAt, refreshToken = response.refreshToken, refreshExpiresAt = response.refreshExpiresAt, + idempotencyKey = UUID.randomUUID().toString(), ) }.mapLeft { error -> Timber.tag(TAG).e("Can not refresh auth tokens: $error") diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultTangemPayRemoteDataSource.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultTangemPayRemoteDataSource.kt index 63d2853352..69fac79358 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultTangemPayRemoteDataSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultTangemPayRemoteDataSource.kt @@ -17,6 +17,7 @@ import com.tangem.domain.visa.model.VisaAuthChallenge import com.tangem.domain.visa.model.VisaAuthSession import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +import java.util.UUID import javax.inject.Inject internal class DefaultTangemPayRemoteDataSource @Inject constructor( @@ -66,6 +67,7 @@ internal class DefaultTangemPayRemoteDataSource @Inject constructor( expiresAt = response.expiresAt, refreshToken = response.refreshToken, refreshExpiresAt = response.refreshExpiresAt, + idempotencyKey = UUID.randomUUID().toString(), ) } } diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/analytics/OnrampAnalyticsEvent.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/analytics/OnrampAnalyticsEvent.kt index d9d98eac03..5010202433 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/analytics/OnrampAnalyticsEvent.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/analytics/OnrampAnalyticsEvent.kt @@ -8,6 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER import com.tangem.core.analytics.models.AnalyticsParam.Key.RESIDENCE import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.domain.onramp.model.OnrampSource sealed class OnrampAnalyticsEvent( @@ -28,7 +29,7 @@ sealed class OnrampAnalyticsEvent( SOURCE to source.analyticsName, TOKEN_PARAM to tokenSymbol, ), - ) + ), AppsFlyerIncludedEvent class SelectCurrencyScreenOpened : OnrampAnalyticsEvent(event = "Currency Screen Opened") @@ -133,7 +134,7 @@ sealed class OnrampAnalyticsEvent( "Currency Type" to currency, PAYMENT_METHOD to paymentMethod, ), - ) + ), AppsFlyerIncludedEvent class MinAmountError : OnrampAnalyticsEvent(event = "Error - Min Amount") class MaxAmountError : OnrampAnalyticsEvent(event = "Error - Max Amount") diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt b/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt index 6784b226dd..cdf1a16bad 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/analytics/StakingAnalyticsEvent.kt @@ -2,6 +2,7 @@ package com.tangem.domain.staking.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.models.staking.action.StakingActionType @@ -21,7 +22,7 @@ sealed class StakingAnalyticsEvent( params = mapOf( "Validators Count" to validatorsCount.toString(), ), - ) + ), AppsFlyerIncludedEvent class WhatIsStaking : StakingAnalyticsEvent( event = "Link - What Is Staking", @@ -40,7 +41,7 @@ sealed class StakingAnalyticsEvent( "Validator" to validator, "Action" to action.asAnalyticName, ), - ) + ), AppsFlyerIncludedEvent data class StakeInProgressScreenOpened( val validator: String, @@ -51,7 +52,7 @@ sealed class StakingAnalyticsEvent( "Validator" to validator, "Action" to action.asAnalyticName, ), - ) + ), AppsFlyerIncludedEvent class RewardScreenOpened : StakingAnalyticsEvent( event = "Reward Screen Opened", diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayAuthTokens.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayAuthTokens.kt index eb704a588a..b50008cf29 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayAuthTokens.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/TangemPayAuthTokens.kt @@ -11,6 +11,7 @@ data class TangemPayAuthTokens( @Json(name = "expires_at") val expiresAt: Long, @Json(name = "refresh_token") val refreshToken: String, @Json(name = "refresh_expires_at") val refreshExpiresAt: Long, + @Json(name = "idempotency_key") val idempotencyKey: String? = null, ) fun TangemPayAuthTokens.getAuthHeader(): String { diff --git a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt index 3e6826fb6a..e03d928530 100644 --- a/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt +++ b/domain/wallet-connect/src/main/kotlin/com/tangem/domain/walletconnect/WcAnalyticEvents.kt @@ -4,6 +4,7 @@ import com.domain.blockaid.models.dapp.CheckDAppResult import com.domain.blockaid.models.dapp.CheckDAppResult.* import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.domain.models.network.Network import com.tangem.domain.walletconnect.WcAnalyticEvents.DAppVerificationStatus import com.tangem.domain.walletconnect.model.WcPairRequest @@ -19,7 +20,8 @@ sealed class WcAnalyticEvents( params: Map = emptyMap(), ) : AnalyticsEvent(category = WC_CATEGORY_NAME, event = event, params = params) { - class ScreenOpened : WcAnalyticEvents(event = "WC Screen Opened") + class ScreenOpened : WcAnalyticEvents(event = "WC Screen Opened"), AppsFlyerIncludedEvent + class NewPairInitiated(source: WcPairRequest.Source) : WcAnalyticEvents( event = "Session Initiated", params = mapOf( @@ -74,7 +76,7 @@ sealed class WcAnalyticEvents( AnalyticsParam.BLOCKCHAIN to sessionForApprove.network.joinToString(",") { it.name }, DOMAIN_VERIFICATION to securityStatus.toAnalyticVerificationStatus(), ), - ) + ), AppsFlyerIncludedEvent class DAppConnectionFailed( errorCode: String, @@ -85,7 +87,7 @@ sealed class WcAnalyticEvents( AnalyticsParam.ERROR_CODE to errorCode, AnalyticsParam.ERROR_DESCRIPTION to errorMessage, ), - ) + ), AppsFlyerIncludedEvent class SessionDisconnected(dAppMetaData: WcAppMetaData) : WcAnalyticEvents( event = "dApp Disconnected", diff --git a/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/CreateWalletStartModel.kt b/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/CreateWalletStartModel.kt index 8c7bb0d2eb..2183fc05a0 100644 --- a/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/CreateWalletStartModel.kt +++ b/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/CreateWalletStartModel.kt @@ -92,7 +92,7 @@ internal class CreateWalletStartModel @Inject constructor( onPrimaryButtonClick = ::onBuyClick, primaryButtonText = resourceReference(R.string.details_buy_wallet), otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title), - otherMethodDescription = resourceReference(R.string.welcome_create_wallet_mobile_description), + otherMethodDescription = null, otherMethodClick = ::onStartWithMobileWalletClick, onBackClick = { router.pop() }, onScanClick = ::onScanClick, diff --git a/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/entity/CreateWalletStartUM.kt b/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/entity/CreateWalletStartUM.kt index b45777a20a..bce2d230b2 100644 --- a/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/entity/CreateWalletStartUM.kt +++ b/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/entity/CreateWalletStartUM.kt @@ -12,7 +12,7 @@ internal data class CreateWalletStartUM( val shouldShowScanSecondaryButton: Boolean, val primaryButtonText: TextReference, val onPrimaryButtonClick: () -> Unit, - val otherMethodDescription: TextReference, + val otherMethodDescription: TextReference?, val otherMethodTitle: TextReference, val otherMethodClick: () -> Unit, val onScanClick: () -> Unit, diff --git a/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/ui/CreateWalletStartContent.kt b/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/ui/CreateWalletStartContent.kt index ecf741b0f0..e113e454a5 100644 --- a/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/ui/CreateWalletStartContent.kt +++ b/features/create-wallet-start/impl/src/main/kotlin/com/tangem/features/createwalletstart/ui/CreateWalletStartContent.kt @@ -181,7 +181,7 @@ internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modi .height(16.dp), ) Text( - text = stringResourceSafe(R.string.welcome_create_wallet_other_method), + text = stringResourceSafe(R.string.common_or), style = TangemTheme.typography.caption1, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, @@ -193,19 +193,21 @@ internal fun CreateWalletStartContent(state: CreateWalletStartUM, modifier: Modi .scale(scaleX = -1f, scaleY = 1f), ) } - Text( - modifier = Modifier - .fillMaxWidth() - .padding( - start = 16.dp, - top = 16.dp, - end = 16.dp, - ), - text = state.otherMethodDescription.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.Center, - ) + if (state.otherMethodDescription != null) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding( + start = 16.dp, + top = 16.dp, + end = 16.dp, + ), + text = state.otherMethodDescription.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + ) + } Row( modifier = Modifier .wrapContentWidth() @@ -429,9 +431,7 @@ private class CreateWalletStartStateProvider : CollectionPreviewParameterProvide onPrimaryButtonClick = { }, primaryButtonText = resourceReference(R.string.details_buy_wallet), otherMethodTitle = resourceReference(R.string.welcome_create_wallet_mobile_title), - otherMethodDescription = resourceReference( - R.string.welcome_create_wallet_mobile_description, - ), + otherMethodDescription = null, otherMethodClick = { }, onBackClick = { }, onScanClick = { }, diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt index 0a9a273352..45c7cd73ca 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewDetailsComponent.kt @@ -12,6 +12,7 @@ import com.tangem.features.details.entity.DetailsUM import com.tangem.features.details.ui.DetailsScreen import com.tangem.features.details.utils.ItemsBuilder import com.tangem.features.details.utils.SocialsBuilder +import com.tangem.features.hotwallet.HotWalletFeatureToggles import kotlinx.coroutines.runBlocking internal class PreviewDetailsComponent : DetailsComponent { @@ -19,9 +20,14 @@ internal class PreviewDetailsComponent : DetailsComponent { private val previewBlocks = runBlocking { ItemsBuilder( router = DummyRouter(), + hotWalletFeatureToggles = object : HotWalletFeatureToggles { + override val isHotWalletEnabled: Boolean = true + override val isWalletCreationRestrictionEnabled: Boolean = true + }, ).buildAll( isWalletConnectAvailable = true, isSupportChatAvailable = true, + hasAnyMobileWallet = true, userWalletId = UserWalletId(""), onSupportEmailClick = {}, onSupportChatClick = {}, diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt index 86ecb2a42f..e56710ead0 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt @@ -50,6 +50,7 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent { ), addNewWalletText = resourceReference(R.string.user_wallet_list_add_button), isWalletSavingInProgress = true, + addNewWalletIconRes = R.drawable.ic_plus_24, onAddNewWalletClick = {}, ), ) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/DetailsItemUM.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/DetailsItemUM.kt index 99c416a63a..5e7ebe0773 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/DetailsItemUM.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/DetailsItemUM.kt @@ -2,6 +2,7 @@ package com.tangem.features.details.entity import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.block.model.BlockUM +import com.tangem.core.ui.extensions.TextReference import kotlinx.collections.immutable.ImmutableList @Immutable @@ -27,4 +28,9 @@ internal sealed class DetailsItemUM { data object UserWalletList : DetailsItemUM() { override val id: String = "user_wallet_list" } + + data class UnderSectionText( + override val id: String, + val text: TextReference, + ) : DetailsItemUM() } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt index a8ef5eb141..bc4baf7d42 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt @@ -1,5 +1,6 @@ package com.tangem.features.details.entity +import androidx.annotation.DrawableRes import androidx.compose.runtime.Immutable import com.tangem.common.ui.userwallet.state.UserWalletItemUM import com.tangem.core.ui.extensions.TextReference @@ -10,5 +11,6 @@ internal data class UserWalletListUM( val userWallets: ImmutableList, val isWalletSavingInProgress: Boolean, val addNewWalletText: TextReference, + @DrawableRes val addNewWalletIconRes: Int?, val onAddNewWalletClick: () -> Unit, ) \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index 1c4c53f372..9d316e6c90 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -97,6 +97,7 @@ internal class DetailsModel @Inject constructor( itemsBuilder.buildAll( isWalletConnectAvailable = isWalletConnectAvailable, isSupportChatAvailable = feedbackFeatureToggles.isUsedeskEnabled, + hasAnyMobileWallet = getWalletsUseCase.invokeSync().any { it is UserWallet.Hot }, userWalletId = params.userWalletId, onSupportEmailClick = ::sendFeedback, onSupportChatClick = ::openUseDesk, diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt index d06b8f5e19..d1a90f171f 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/UserWalletListModel.kt @@ -57,6 +57,7 @@ internal class UserWalletListModel @Inject constructor( isWalletSavingInProgress = false, addNewWalletText = TextReference.EMPTY, onAddNewWalletClick = ::onAddNewWalletClick, + addNewWalletIconRes = R.drawable.ic_plus_24, ), ) @@ -83,10 +84,11 @@ internal class UserWalletListModel @Inject constructor( value.copy( userWallets = userWallets, isWalletSavingInProgress = isWalletSavingInProgress, - addNewWalletText = if (shouldSaveUserWallets || hotWalletFeatureToggles.isHotWalletEnabled) { - resourceReference(R.string.user_wallet_list_add_button) - } else { - resourceReference(R.string.scan_card_settings_button) + addNewWalletText = when { + shouldSaveUserWallets || hotWalletFeatureToggles.isHotWalletEnabled -> { + resourceReference(R.string.user_wallet_list_add_button) + } + else -> resourceReference(R.string.scan_card_settings_button) }, ) } @@ -94,7 +96,14 @@ internal class UserWalletListModel @Inject constructor( private fun onAddNewWalletClick() { if (hotWalletFeatureToggles.isHotWalletEnabled) { analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.Settings)) - router.push(AppRoute.CreateWalletSelection) + + if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled) { + withProgress(isWalletSavingInProgress) { + userWalletSaver.scanAndSaveUserWallet(modelScope) + } + } else { + router.push(AppRoute.CreateWalletSelection) + } } else { withProgress(isWalletSavingInProgress) { userWalletSaver.scanAndSaveUserWallet(modelScope) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt index 6be90f525b..0afe3711b2 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/DetailsScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState @@ -18,10 +19,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.block.BlockItem import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -68,7 +73,7 @@ private fun Content( ) { LazyColumn( modifier = modifier.testTag(DetailsScreenTestTags.SCREEN_CONTAINER), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), + verticalArrangement = Arrangement.SpaceBetween, contentPadding = PaddingValues( top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing16, @@ -94,6 +99,7 @@ private fun Content( } item(key = "footer") { + SpacerH16() Footer( modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing12), model = state.footer, @@ -108,6 +114,16 @@ private fun Block( userWalletListBlockContent: ComposableContentComponent, modifier: Modifier = Modifier, ) { + if (model is DetailsItemUM.UnderSectionText) { + UnderSectionTextBlock( + modifier = Modifier.fillMaxWidth(), + text = model.text, + ) + return + } + + SpacerH16() + Column( modifier = modifier .fillMaxWidth() @@ -142,10 +158,21 @@ private fun Block( is DetailsItemUM.UserWalletList -> { userWalletListBlockContent.Content(modifier = itemModifier) } + is DetailsItemUM.UnderSectionText -> { /* Handled above */ } } } } +@Composable +private fun UnderSectionTextBlock(text: TextReference, modifier: Modifier = Modifier) { + Text( + modifier = modifier.padding(horizontal = 30.dp, vertical = 8.dp), + text = text.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) +} + @Composable private fun Footer(model: DetailsFooterUM, modifier: Modifier = Modifier) { Column( diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt index ec27900868..f06856c4df 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt @@ -1,6 +1,7 @@ package com.tangem.features.details.ui import android.content.res.Configuration +import androidx.annotation.DrawableRes import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.* import androidx.compose.material3.CircularProgressIndicator @@ -23,7 +24,6 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.details.component.UserWalletListComponent import com.tangem.features.details.component.preview.PreviewUserWalletListComponent import com.tangem.features.details.entity.UserWalletListUM -import com.tangem.features.details.impl.R @Composable internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = Modifier) { @@ -42,6 +42,7 @@ internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = M text = state.addNewWalletText, isInProgress = state.isWalletSavingInProgress, onClick = state.onAddNewWalletClick, + icon = state.addNewWalletIconRes, ) } } @@ -49,6 +50,7 @@ internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = M @Composable private fun AddWalletButton( text: TextReference, + @DrawableRes icon: Int?, isInProgress: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, @@ -65,23 +67,25 @@ private fun AddWalletButton( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { - AnimatedContent( - modifier = Modifier.size(TangemTheme.dimens.size24), - targetState = isInProgress, - label = "Add wallet progress", - ) { isInProgress -> - if (isInProgress) { - CircularProgressIndicator( - modifier = Modifier.size(TangemTheme.dimens.size24), - color = TangemTheme.colors.icon.accent, - ) - } else { - Icon( - modifier = Modifier.size(TangemTheme.dimens.size24), - painter = painterResource(id = R.drawable.ic_plus_24), - tint = TangemTheme.colors.icon.accent, - contentDescription = null, - ) + if (icon != null) { + AnimatedContent( + modifier = Modifier.size(TangemTheme.dimens.size24), + targetState = isInProgress, + label = "Add wallet progress", + ) { isInProgress -> + if (isInProgress) { + CircularProgressIndicator( + modifier = Modifier.size(TangemTheme.dimens.size24), + color = TangemTheme.colors.icon.accent, + ) + } else { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = icon), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + } } } diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt index 7ee2b6eccd..9746070c35 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/ItemsBuilder.kt @@ -8,6 +8,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.details.entity.DetailsItemUM import com.tangem.features.details.impl.R +import com.tangem.features.hotwallet.HotWalletFeatureToggles import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -17,12 +18,16 @@ import javax.inject.Inject private const val TANGEM_PAY_ITEM_ID = "get_tangem_pay" @ModelScoped -internal class ItemsBuilder @Inject constructor(private val router: Router) { +internal class ItemsBuilder @Inject constructor( + private val router: Router, + private val hotWalletFeatureToggles: HotWalletFeatureToggles, +) { @Suppress("LongParameterList") fun buildAll( isWalletConnectAvailable: Boolean, isSupportChatAvailable: Boolean, + hasAnyMobileWallet: Boolean, userWalletId: UserWalletId, onSupportEmailClick: () -> Unit, onSupportChatClick: () -> Unit, @@ -30,6 +35,14 @@ internal class ItemsBuilder @Inject constructor(private val router: Router) { ): ImmutableList = buildList { buildWalletConnectBlock(isWalletConnectAvailable, userWalletId)?.let(::add) buildUserWalletListBlock().let(::add) + + if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled && hasAnyMobileWallet) { + DetailsItemUM.UnderSectionText( + id = "only_one_mobile_wallet_explanation", + text = resourceReference(R.string.only_one_mobile_wallet_explanation), + ).let(::add) + } + buildShopBlock(onBuyClick).let(::add) buildSettingsBlock().let(::add) buildSupportBlock( diff --git a/features/hot-wallet/api/build.gradle.kts b/features/hot-wallet/api/build.gradle.kts index a51cd40254..7e7bd837fd 100644 --- a/features/hot-wallet/api/build.gradle.kts +++ b/features/hot-wallet/api/build.gradle.kts @@ -9,6 +9,7 @@ android { } dependencies { + implementation(projects.common.routing) /* Project - Domain */ implementation(projects.domain.models) diff --git a/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/HotWalletFeatureToggles.kt b/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/HotWalletFeatureToggles.kt index f8ecf58ffe..c0526863e1 100644 --- a/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/HotWalletFeatureToggles.kt +++ b/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/HotWalletFeatureToggles.kt @@ -2,4 +2,5 @@ package com.tangem.features.hotwallet interface HotWalletFeatureToggles { val isHotWalletEnabled: Boolean + val isWalletCreationRestrictionEnabled: Boolean } \ No newline at end of file diff --git a/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/WalletBackupComponent.kt b/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/WalletBackupComponent.kt index c5c270623f..0d4df73fb4 100644 --- a/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/WalletBackupComponent.kt +++ b/features/hot-wallet/api/src/main/kotlin/com/tangem/features/hotwallet/WalletBackupComponent.kt @@ -8,6 +8,7 @@ interface WalletBackupComponent : ComposableContentComponent { data class Params( val userWalletId: UserWalletId, + val isColdWalletOptionShown: Boolean, ) interface Factory : ComponentFactory diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/DefaultHotWalletFeatureToggles.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/DefaultHotWalletFeatureToggles.kt index cadb09331f..b635ab78db 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/DefaultHotWalletFeatureToggles.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/DefaultHotWalletFeatureToggles.kt @@ -7,4 +7,7 @@ internal class DefaultHotWalletFeatureToggles( ) : HotWalletFeatureToggles { override val isHotWalletEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_ENABLED") + override val isWalletCreationRestrictionEnabled: Boolean + get() = isHotWalletEnabled && + featureTogglesManager.isFeatureEnabled(name = "HOT_WALLET_CREATION_RESTRICTION_ENABLED") } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/common/ui/OptionBlock.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/common/ui/OptionBlock.kt index bcc2a2c440..168491ff06 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/common/ui/OptionBlock.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/common/ui/OptionBlock.kt @@ -26,11 +26,11 @@ private const val DISABLED_COLORS_ALPHA = 0.5f internal fun OptionBlock( title: String, description: String, - badge: (@Composable () -> Unit)?, onClick: (() -> Unit)?, - enabled: Boolean, - backgroundColor: Color, modifier: Modifier = Modifier, + backgroundColor: Color = TangemTheme.colors.background.primary, + badge: (@Composable () -> Unit)? = null, + enabled: Boolean = true, ) { Column( modifier = modifier diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/ui/CreateHardwareWalletContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/ui/CreateHardwareWalletContent.kt index 095790c30a..08afdafd30 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/ui/CreateHardwareWalletContent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/ui/CreateHardwareWalletContent.kt @@ -66,7 +66,7 @@ internal fun CreateHardwareWalletContent(state: CreateHardwareWalletUM, modifier top = 20.dp, end = 16.dp, ), - text = stringResourceSafe(R.string.wallet_create_common_title), + text = stringResourceSafe(R.string.hardware_wallet_create_title), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, @@ -74,22 +74,22 @@ internal fun CreateHardwareWalletContent(state: CreateHardwareWalletUM, modifier FeatureBlock( modifier = Modifier .padding(top = 32.dp), - title = stringResourceSafe(R.string.hw_upgrade_key_migration_title), - description = stringResourceSafe(R.string.hw_upgrade_key_migration_description), - iconRes = R.drawable.ic_mobile_security_24, + title = stringResourceSafe(R.string.hardware_wallet_key_feature_title), + description = stringResourceSafe(R.string.hardware_wallet_key_feature_description), + iconRes = R.drawable.ic_mobile_security_2_24, ) FeatureBlock( modifier = Modifier .padding(top = 24.dp), - title = stringResourceSafe(R.string.hw_upgrade_funds_access_title), - description = stringResourceSafe(R.string.hw_upgrade_funds_access_description), - iconRes = R.drawable.ic_knight_shield_24, + title = stringResourceSafe(R.string.hardware_wallet_backup_feature_title), + description = stringResourceSafe(R.string.hardware_wallet_backup_feature_description), + iconRes = R.drawable.ic_double_star_24, ) FeatureBlock( modifier = Modifier .padding(top = 24.dp), - title = stringResourceSafe(R.string.hw_upgrade_general_security_title), - description = stringResourceSafe(R.string.hw_upgrade_general_security_description), + title = stringResourceSafe(R.string.hardware_wallet_security_feature_title), + description = stringResourceSafe(R.string.hardware_wallet_security_feature_description), iconRes = R.drawable.ic_protect_24, ) } diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt index 1d9409b60b..d268abddb8 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt @@ -61,7 +61,6 @@ internal class CreateMobileWalletModel @Inject constructor( init { trackingContextProxy.addHotWalletContext() analyticsEventHandler.send(event = OnboardingAnalyticsEvent.Onboarding.Started(source = params.source)) - analyticsEventHandler.send(event = OnboardingAnalyticsEvent.Onboarding.AppsFlyerOnlyEntryScreenView()) analyticsEventHandler.send( event = OnboardingAnalyticsEvent.SeedPhrase.CreateMobileScreenOpened(source = params.source), ) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/ui/CreateMobileWalletContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/ui/CreateMobileWalletContent.kt index e6916bdcfd..bf0f02e9b8 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/ui/CreateMobileWalletContent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/ui/CreateMobileWalletContent.kt @@ -75,14 +75,21 @@ internal fun CreateMobileWalletContent(state: CreateMobileWalletUM, modifier: Mo .padding(top = 32.dp), title = stringResourceSafe(R.string.hw_create_keys_title), description = stringResourceSafe(R.string.hw_create_keys_description), - iconRes = R.drawable.ic_lock_24, + iconRes = R.drawable.ic_protect_24, ) FeatureBlock( modifier = Modifier .padding(top = 24.dp), title = stringResourceSafe(R.string.hw_create_seed_title), description = stringResourceSafe(R.string.hw_create_seed_description), - iconRes = R.drawable.ic_settings_24, + iconRes = R.drawable.ic_seed_phrase_24, + ) + FeatureBlock( + modifier = Modifier + .padding(top = 24.dp), + title = stringResourceSafe(R.string.hw_create_upgrade_title), + description = stringResourceSafe(R.string.hw_create_upgrade_description), + iconRes = R.drawable.ic_tangem_card_24, ) } SecondaryButton( diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/upgradewallet/ui/UpgradeWalletContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/upgradewallet/ui/UpgradeWalletContent.kt index a643ee75e0..7dc3ca93cf 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/upgradewallet/ui/UpgradeWalletContent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/upgradewallet/ui/UpgradeWalletContent.kt @@ -83,9 +83,9 @@ internal fun UpgradeWalletContent(state: UpgradeWalletUM, modifier: Modifier = M FeatureBlock( modifier = Modifier .padding(top = 24.dp), - title = stringResourceSafe(R.string.hw_upgrade_funds_access_title), - description = stringResourceSafe(R.string.hw_upgrade_funds_access_description), - iconRes = R.drawable.ic_knight_shield_24, + title = stringResourceSafe(R.string.hw_upgrade_backup_title), + description = stringResourceSafe(R.string.hw_upgrade_backup_description), + iconRes = R.drawable.ic_smartphone_24, ) FeatureBlock( modifier = Modifier diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/model/ViewPhraseModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/model/ViewPhraseModel.kt index a0b991c0a4..808f1df0b6 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/model/ViewPhraseModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/viewphrase/model/ViewPhraseModel.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @Stable @@ -48,19 +49,19 @@ internal class ViewPhraseModel @Inject constructor( private fun loadSeedPhrase() { val userWallet = getUserWalletUseCase(params.userWalletId) .getOrElse { error("User wallet with id ${params.userWalletId} not found") } - if (userWallet is UserWallet.Hot) { - modelScope.launch { - val words = exportSeedPhraseUseCase.invoke(userWallet.hotWalletId) - .getOrElse { error("Unable to export seed phrase for wallet with id ${params.userWalletId}") } - .mnemonic - .mnemonicComponents - uiState.update { - it.copy( - words = words.mapIndexed { index, s -> - EnumeratedTwoColumnGridItem(index + 1, s) - }.toImmutableList(), - ) - } + as? UserWallet.Hot ?: return + + modelScope.launch { + val words = exportSeedPhraseUseCase.invoke(userWallet.hotWalletId) + .getOrElse { error -> Timber.e(error); throw error } + .mnemonic.mnemonicComponents + + uiState.update { + it.copy( + words = words.mapIndexed { index, s -> + EnumeratedTwoColumnGridItem(index + 1, s) + }.toImmutableList(), + ) } } } diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/entity/WalletBackupUM.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/entity/WalletBackupUM.kt index 9afce42e0d..a1219ed46a 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/entity/WalletBackupUM.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/entity/WalletBackupUM.kt @@ -8,7 +8,6 @@ internal data class WalletBackupUM( val recoveryPhraseOption: LabelUM?, val googleDriveOption: LabelUM?, val googleDriveStatus: BackupStatus, - val onBuyClick: () -> Unit, val onRecoveryPhraseClick: () -> Unit, val onGoogleDriveClick: () -> Unit, val onHardwareWalletClick: () -> Unit, diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModel.kt index bdc95378ca..7c284f9cef 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/model/WalletBackupModel.kt @@ -2,21 +2,17 @@ package com.tangem.features.hotwallet.walletbackup.model import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.utils.TrackingContextProxy 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.navigation.url.UrlOpener import com.tangem.core.ui.R import com.tangem.core.ui.components.label.entity.LabelStyle import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents -import com.tangem.domain.wallets.usecase.GenerateBuyTangemCardLinkUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.UnlockHotWalletContextualUseCase import com.tangem.features.hotwallet.WalletBackupComponent @@ -36,8 +32,6 @@ internal class WalletBackupModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val getUserWalletUseCase: GetUserWalletUseCase, private val unlockHotWalletContextualUseCase: UnlockHotWalletContextualUseCase, - private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase, - private val urlOpener: UrlOpener, private val router: Router, private val trackingContextProxy: TrackingContextProxy, private val analyticsEventHandler: AnalyticsEventHandler, @@ -54,7 +48,7 @@ internal class WalletBackupModel @Inject constructor( hardwareWalletOption = LabelUM( text = resourceReference(R.string.common_recommended), style = LabelStyle.ACCENT, - ), + ).takeIf { params.isColdWalletOptionShown }, recoveryPhraseOption = LabelUM( text = resourceReference(R.string.hw_backup_no_backup), style = LabelStyle.WARNING, @@ -64,7 +58,6 @@ internal class WalletBackupModel @Inject constructor( style = LabelStyle.REGULAR, ), googleDriveStatus = BackupStatus.ComingSoon, - onBuyClick = ::onBuyClick, onRecoveryPhraseClick = ::onRecoveryPhraseClick, onGoogleDriveClick = { }, onHardwareWalletClick = ::onHardwareWalletClick, @@ -129,14 +122,6 @@ internal class WalletBackupModel @Inject constructor( backedUp = userWallet.backedUp, ) - private fun onBuyClick() { - analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Backup)) - modelScope.launch { - generateBuyTangemCardLinkUseCase - .invoke(GenerateBuyTangemCardLinkUseCase.Source.Backup).let { urlOpener.openUrl(it) } - } - } - private fun onRecoveryPhraseClick() { analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonRecoveryPhrase()) if (uiState.value.backedUp) { @@ -157,10 +142,9 @@ internal class WalletBackupModel @Inject constructor( ) } else { router.push( - AppRoute.CreateWalletBackup( + AppRoute.WalletActivation( userWalletId = params.userWalletId, - analyticsSource = AnalyticsParam.ScreensSources.Backup.value, - analyticsAction = WalletSettingsAnalyticEvents.RecoveryPhraseScreenAction.Backup.value, + isBackupExists = false, ), ) } diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/ui/WalletBackupContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/ui/WalletBackupContent.kt index d3e452bab2..dc2787f9c6 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/ui/WalletBackupContent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/walletbackup/ui/WalletBackupContent.kt @@ -1,36 +1,25 @@ package com.tangem.features.hotwallet.walletbackup.ui import android.content.res.Configuration -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.compose.ui.unit.dp import com.tangem.core.ui.R -import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.label.Label import com.tangem.core.ui.components.label.entity.LabelStyle import com.tangem.core.ui.components.label.entity.LabelUM import com.tangem.core.ui.components.rows.NetworkTitle -import com.tangem.core.ui.extensions.TextReference -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.res.ForceDarkTheme import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.hotwallet.common.ui.OptionBlock @@ -61,33 +50,31 @@ internal fun WalletBackupContent(state: WalletBackupUM, modifier: Modifier = Mod end = 16.dp, ), ) { - Banner(state) + if (state.hardwareWalletOption != null) { + OptionBlock( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + title = stringResourceSafe(R.string.hw_backup_upgrade_title), + description = stringResourceSafe(R.string.hw_backup_upgrade_description), + badge = { Label(state.hardwareWalletOption) }, + onClick = state.onHardwareWalletClick, + enabled = true, + backgroundColor = TangemTheme.colors.background.primary, + ) - OptionBlock( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - title = stringResourceSafe(R.string.hw_backup_hardware_title), - description = stringResourceSafe(R.string.hw_backup_hardware_description), - badge = { - state.hardwareWalletOption?.let { Label(it) } - }, - onClick = state.onHardwareWalletClick, - enabled = true, - backgroundColor = TangemTheme.colors.background.primary, - ) - NetworkTitle( - modifier = Modifier - .padding(top = 8.dp), - title = { - Text( - modifier = Modifier, - text = stringResourceSafe(R.string.onboarding_create_wallet_options_button_options), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) - }, - ) + NetworkTitle( + modifier = Modifier.padding(top = 8.dp), + title = { + Text( + modifier = Modifier, + text = stringResourceSafe(R.string.onboarding_create_wallet_options_button_options), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + }, + ) + } OptionBlock( modifier = Modifier, title = stringResourceSafe(R.string.hw_backup_seed_title), @@ -116,120 +103,6 @@ internal fun WalletBackupContent(state: WalletBackupUM, modifier: Modifier = Mod } } -@Suppress("LongMethod") -@OptIn(ExperimentalLayoutApi::class) -@Composable -private fun Banner(state: WalletBackupUM, modifier: Modifier = Modifier) { - ForceDarkTheme { - Column( - modifier = modifier - .background( - color = TangemTheme.colors.background.primary, - shape = RoundedCornerShape(16.dp), - ), - ) { - Column( - modifier = Modifier - .padding( - start = 12.dp, - top = 20.dp, - end = 12.dp, - ), - ) { - Text( - modifier = Modifier - .fillMaxWidth(), - text = "Tangem Wallet", - style = TangemTheme.typography.h3, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - ) - - Text( - modifier = Modifier - .fillMaxWidth() - .padding( - top = 4.dp, - ), - text = "Keeps your crypto safe and offline. Slim as a credit card, safer than a bank vault.", - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.Center, - ) - - FlowRow( - modifier = Modifier - .fillMaxWidth() - .padding( - start = 24.dp, - top = 16.dp, - end = 24.dp, - ), - horizontalArrangement = Arrangement.Center, - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - FeatureItem( - iconResId = R.drawable.ic_shield_check_16, - text = resourceReference(R.string.welcome_create_wallet_feature_class), - ) - FeatureItem( - iconResId = R.drawable.ic_flash_16, - text = resourceReference(R.string.welcome_create_wallet_feature_delivery), - ) - FeatureItem( - iconResId = R.drawable.ic_sparkles_16, - text = resourceReference(R.string.welcome_create_wallet_feature_use), - ) - } - - Box( - modifier = Modifier - .padding( - start = 8.dp, - top = 12.dp, - end = 8.dp, - bottom = 20.dp, - ), - ) { - Image( - painter = painterResource(id = R.drawable.img_tangem_cards_vertical), - contentDescription = null, - ) - SecondaryButton( - modifier = Modifier - .fillMaxWidth() - .align(Alignment.BottomCenter), - text = stringResourceSafe(R.string.details_buy_wallet), - onClick = state.onBuyClick, - ) - } - } - } - } -} - -@Composable -private fun FeatureItem(@DrawableRes iconResId: Int, text: TextReference) { - Row( - modifier = Modifier - .wrapContentWidth() - .padding(horizontal = 8.dp), - horizontalArrangement = Arrangement.spacedBy(6.dp), - ) { - Icon( - modifier = Modifier.size(16.dp), - painter = painterResource(iconResId), - tint = TangemTheme.colors.icon.accent, - contentDescription = null, - ) - Text( - text = text.resolveReference(), - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.secondary, - ) - } -} - @Preview(showBackground = true, widthDp = 360) @Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable @@ -256,7 +129,6 @@ private class WalletBackupUMProvider : CollectionPreviewParameterProvider, val onBackClick: () -> Unit, val onBuyClick: () -> Unit, diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/wallethardwarebackup/model/WalletHardwareBackupModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/wallethardwarebackup/model/WalletHardwareBackupModel.kt index efcd39aa14..ae34bf84a6 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/wallethardwarebackup/model/WalletHardwareBackupModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/wallethardwarebackup/model/WalletHardwareBackupModel.kt @@ -32,10 +32,8 @@ import com.tangem.features.hotwallet.wallethardwarebackup.entity.WalletHardwareB import com.tangem.hot.sdk.model.HotWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -119,7 +117,6 @@ internal class WalletHardwareBackupModel @Inject constructor( init { trackingContextProxy.addHotWalletContext() analyticsEventHandler.send(WalletSettingsAnalyticEvents.HardwareBackupScreenOpened()) - showPurchaseBlockWithDelay() } override fun onDestroy() { @@ -127,13 +124,6 @@ internal class WalletHardwareBackupModel @Inject constructor( super.onDestroy() } - private fun showPurchaseBlockWithDelay() { - modelScope.launch { - delay(SHOW_PURCHASE_BLOCK_DELAY) - uiState.update { it.copy(showPurchaseBlock = true) } - } - } - private fun onCreateNewWalletClick() { analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonCreateNewWallet()) router.push(AppRoute.CreateHardwareWallet) @@ -175,8 +165,4 @@ internal class WalletHardwareBackupModel @Inject constructor( .invoke(GenerateBuyTangemCardLinkUseCase.Source.Backup).let { urlOpener.openUrl(it) } } } - - companion object { - private const val SHOW_PURCHASE_BLOCK_DELAY = 3000L - } } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/wallethardwarebackup/ui/WalletHardwareBackupContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/wallethardwarebackup/ui/WalletHardwareBackupContent.kt index 8e4eafbf78..41073ffc50 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/wallethardwarebackup/ui/WalletHardwareBackupContent.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/wallethardwarebackup/ui/WalletHardwareBackupContent.kt @@ -1,26 +1,26 @@ package com.tangem.features.hotwallet.wallethardwarebackup.ui import android.content.res.Configuration -import androidx.compose.animation.AnimatedVisibility +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SecondaryButton -import com.tangem.core.ui.components.buttons.common.TangemButtonSize import com.tangem.core.ui.components.label.Label import com.tangem.core.ui.components.label.entity.LabelStyle import com.tangem.core.ui.components.label.entity.LabelUM -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.res.ForceDarkTheme import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.hotwallet.common.ui.OptionBlock @@ -71,6 +71,8 @@ internal fun WalletHardwareBackupContent(state: WalletHardwareBackupUM, modifier end = 16.dp, ), ) { + Banner(onBuyClick = state.onBuyClick) + state.blocks.forEach { block -> OptionBlock( modifier = Modifier @@ -86,45 +88,119 @@ internal fun WalletHardwareBackupContent(state: WalletHardwareBackupUM, modifier ) } } - AnimatedVisibility(state.showPurchaseBlock) { - PurchaseBlock( - onBuyClick = state.onBuyClick, - ) + } +} + +@Suppress("LongMethod") +@OptIn(ExperimentalLayoutApi::class) +@Composable +private fun Banner(onBuyClick: () -> Unit, modifier: Modifier = Modifier) { + ForceDarkTheme { + Column( + modifier = modifier + .background( + color = TangemTheme.colors.background.primary, + shape = RoundedCornerShape(16.dp), + ), + ) { + Column( + modifier = Modifier + .padding( + start = 12.dp, + top = 20.dp, + end = 12.dp, + ), + ) { + Text( + modifier = Modifier + .fillMaxWidth(), + text = stringResourceSafe(R.string.common_tangem_wallet), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + + Text( + modifier = Modifier + .fillMaxWidth() + .padding( + top = 4.dp, + ), + text = stringResourceSafe(R.string.hw_backup_banner_description), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + ) + + FlowRow( + modifier = Modifier + .fillMaxWidth() + .padding( + start = 24.dp, + top = 16.dp, + end = 24.dp, + ), + horizontalArrangement = Arrangement.Center, + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + FeatureItem( + iconResId = R.drawable.ic_shield_check_16, + text = resourceReference(R.string.welcome_create_wallet_feature_class), + ) + FeatureItem( + iconResId = R.drawable.ic_flash_16, + text = resourceReference(R.string.welcome_create_wallet_feature_delivery), + ) + FeatureItem( + iconResId = R.drawable.ic_sparkles_16, + text = resourceReference(R.string.welcome_create_wallet_feature_seedphrase), + ) + } + + Box( + modifier = Modifier + .padding( + start = 8.dp, + top = 12.dp, + end = 8.dp, + bottom = 20.dp, + ), + ) { + Image( + painter = painterResource(id = R.drawable.img_tangem_cards_vertical), + contentDescription = null, + ) + SecondaryButton( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter), + text = stringResourceSafe(R.string.details_buy_wallet), + onClick = onBuyClick, + ) + } + } } } } @Composable -private fun PurchaseBlock(onBuyClick: () -> Unit, modifier: Modifier = Modifier) { +private fun FeatureItem(@DrawableRes iconResId: Int, text: TextReference) { Row( - modifier = modifier - .fillMaxWidth() - .padding(16.dp) - .background( - color = TangemTheme.colors.background.primary, - shape = TangemTheme.shapes.roundedCornersXMedium, - ) - .padding( - horizontal = 20.dp, - vertical = 16.dp, - ), - verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .wrapContentWidth() + .padding(horizontal = 8.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), ) { - Text( - modifier = Modifier - .weight(1f) - .padding(end = 16.dp), - text = stringResourceSafe(R.string.wallet_add_hardware_purchase), - style = TangemTheme.typography.button, - color = TangemTheme.colors.text.primary1, - maxLines = 3, - overflow = TextOverflow.Ellipsis, + Icon( + modifier = Modifier.size(16.dp), + painter = painterResource(iconResId), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, ) - - SecondaryButton( - text = stringResourceSafe(R.string.wallet_import_buy_title), - onClick = onBuyClick, - size = TangemButtonSize.RoundedAction, + Text( + text = text.resolveReference(), + style = TangemTheme.typography.caption1, + color = TangemTheme.colors.text.secondary, ) } } @@ -156,7 +232,6 @@ private fun PreviewWalletHardwareBackupContent() { onClick = { }, ), ), - showPurchaseBlock = true, onBuyClick = { }, ), ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/analytics/OnboardingEvent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/analytics/OnboardingEvent.kt index 101eb5ff04..b8ed2c5101 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/analytics/OnboardingEvent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/common/analytics/OnboardingEvent.kt @@ -34,12 +34,7 @@ sealed class OnboardingEvent( put("Seed Phrase Length", seedPhraseLength.toString()) } }, - ), AppsFlyerIncludedEvent { - override val appsFlyerReplacedEvent = when (creationType) { - WalletCreationType.NewSeed, WalletCreationType.PrivateKey -> "wallet_created_successfully" - WalletCreationType.SeedImport -> "wallet_imported" - } - } + ), AppsFlyerIncludedEvent sealed class WalletCreationType(val value: String) { data object PrivateKey : WalletCreationType(value = "Private Key") @@ -92,7 +87,6 @@ sealed class OnboardingEvent( ) : OnboardingEvent("Onboarding / Twins", event, params) { class ScreenOpened : Twins("Twinning Screen Opened") - class SetupStarted : Twins("Twin Setup Started") class SetupFinished : Twins("Twin Setup Finished") } diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/analytics/CommonSendAnalyticEvents.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/analytics/CommonSendAnalyticEvents.kt index d50adee6f6..401205f025 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/analytics/CommonSendAnalyticEvents.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/analytics/CommonSendAnalyticEvents.kt @@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE import com.tangem.core.analytics.models.AnalyticsParam.Key.SOURCE import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent /** * Send analytics @@ -25,7 +26,7 @@ sealed class CommonSendAnalyticEvents( params = mapOf( SOURCE to source.analyticsName, ), - ) + ), AppsFlyerIncludedEvent /** Amount screen opened */ data class AmountScreenOpened( @@ -37,7 +38,7 @@ sealed class CommonSendAnalyticEvents( params = mapOf( SOURCE to source.analyticsName, ), - ) + ), AppsFlyerIncludedEvent /** Fee screen opened */ data class FeeScreenOpened( @@ -74,7 +75,7 @@ sealed class CommonSendAnalyticEvents( ) } }, - ) + ), AppsFlyerIncludedEvent /** If transaction delays notification is present */ data class NoticeTransactionDelays( diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/analytics/SendAnalyticEvents.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/analytics/SendAnalyticEvents.kt index d8ccef5d07..c732a34b77 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/analytics/SendAnalyticEvents.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/analytics/SendAnalyticEvents.kt @@ -8,6 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.ENS_ADDRESS import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TYPE import com.tangem.core.analytics.models.AnalyticsParam.Key.NONCE import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.core.ui.extensions.capitalize import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents @@ -41,7 +42,7 @@ internal sealed class SendAnalyticEvents( } put(ENS_ADDRESS, ensAddress) }, - ) + ), AppsFlyerIncludedEvent data class ConvertTokenButtonClicked( val token: String, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt index 4c18c81642..a748635db5 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt @@ -29,6 +29,7 @@ import com.tangem.core.ui.components.inputrow.InputRowRecipient import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.test.SendAddressScreenTestTags +import com.tangem.core.ui.utils.GlobalMultipleClickPreventer import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationRecipientListUM import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationTextFieldUM import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM @@ -138,7 +139,11 @@ private fun LazyListScope.addressItem( title = address.label, placeholder = address.placeholder, onValueChange = { onAddressChange(it, EnterAddressSource.InputField) }, - onPasteClick = { onAddressChange(it, EnterAddressSource.PasteButton) }, + onPasteClick = { + GlobalMultipleClickPreventer.processEvent { + onAddressChange(it, EnterAddressSource.PasteButton) + } + }, onQrCodeClick = onQrCodeClick, isError = isError, isLoading = isValidating, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt index e7e6ac5e7d..8f48f4568b 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt @@ -8,6 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_TOKEN +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents @@ -31,7 +32,7 @@ internal sealed class SendWithSwapAnalyticEvents( SEND_BLOCKCHAIN to fromToken.network.name, RECEIVE_BLOCKCHAIN to toToken.network.name, ), - ) + ), AppsFlyerIncludedEvent data class NoticeCanNotSwapToken( val fromToken: CryptoCurrency, diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt index 92e8672bb2..fe69da29ce 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt @@ -7,6 +7,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_TOKEN import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_TOKEN +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.ui.FeeType @@ -21,7 +22,7 @@ sealed class SwapEvents( data class SwapScreenOpened(val token: String) : SwapEvents( event = "Swap Screen Opened", params = mapOf("Token" to token), - ) + ), AppsFlyerIncludedEvent class SendTokenBalanceClicked : SwapEvents(event = "Send Token Balance Clicked") @@ -96,7 +97,7 @@ sealed class SwapEvents( "Receive Blockchain" to receiveBlockchain, "Account Derivation From or To (optional)" to "$fromDerivationIndex, $toDerivationIndex", ), - ) + ), AppsFlyerIncludedEvent class ProviderClicked : SwapEvents("Provider Clicked") diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt index 34449ac010..4fa7a53f60 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt @@ -65,7 +65,6 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent { onNotificationsDescriptionClick = {}, isNotificationsPermissionGranted = false, onAccessCodeClick = {}, - onUpgradeWalletClick = {}, onBackupClick = {}, onCardSettingsClick = {}, accountsUM = previewAccounts(), diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/WalletSettingsItemUM.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/WalletSettingsItemUM.kt index a495db9417..d9ff609d19 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/WalletSettingsItemUM.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/entity/WalletSettingsItemUM.kt @@ -31,6 +31,7 @@ internal sealed class WalletSettingsItemUM { val text: TextReference, val isEnabled: Boolean, val imageState: ImageState, + val additionalBlock: BlockUM? = null, val onClick: () -> Unit, ) : WalletSettingsItemUM() @@ -46,13 +47,6 @@ internal sealed class WalletSettingsItemUM { val title: TextReference, val description: TextReference, ) : WalletSettingsItemUM() - - data class UpgradeWallet( - override val id: String, - val title: TextReference, - val description: TextReference, - val onClick: () -> Unit, - ) : WalletSettingsItemUM() } @Immutable diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index 31edff8e10..a403130ffa 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -96,7 +96,10 @@ internal class WalletSettingsModel @Inject constructor( val params: WalletSettingsComponent.Params = paramsContainer.require() val dialogNavigation = SlotNavigation() val bottomSheetNavigation: SlotNavigation = SlotNavigation() - private val walletCardItemDelegate = walletCardItemDelegateFactory.create(dialogNavigation) + private val walletCardItemDelegate = walletCardItemDelegateFactory.create( + dialogNavigation = dialogNavigation, + onUpgradeHotWalletClick = ::onUpgradeWalletClick, + ) val state: MutableStateFlow = MutableStateFlow( value = WalletSettingsUM( @@ -249,7 +252,6 @@ internal class WalletSettingsModel @Inject constructor( onCheckedNotificationsChanged = ::onCheckedNotificationsChange, onNotificationsDescriptionClick = ::onNotificationsDescriptionClick, onAccessCodeClick = { onAccessCodeClick(userWallet) }, - onUpgradeWalletClick = { onUpgradeWalletClick() }, onBackupClick = ::onBackupClick, onCardSettingsClick = ::onCardSettingsClick, accountsUM = accountList, @@ -374,10 +376,7 @@ internal class WalletSettingsModel @Inject constructor( val isCodeSet = userWallet.hotWalletId.authType != HotWalletId.AuthType.NoPassword analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonAccessCode(isCodeSet)) if (!state.value.isWalletBackedUp) { - showMakeBackupAtFirstAlertBS( - isUpgradeFlow = false, - action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.AccessCode, - ) + showMakeBackupAtFirstAlertBS() } else { unlockWalletIfNeedAndProceed { authorizationRequired -> router.push( @@ -392,35 +391,28 @@ internal class WalletSettingsModel @Inject constructor( } private fun onUpgradeWalletClick() { - if (!state.value.isWalletBackedUp) { - showMakeBackupAtFirstAlertBS( - isUpgradeFlow = true, - action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.Upgrade, - ) - } else { - unlockWalletIfNeedAndProceed { - router.push(AppRoute.UpgradeWallet(userWalletId = params.userWalletId)) - } - } + router.push(AppRoute.WalletHardwareBackup(userWalletId = params.userWalletId)) } private fun onBackupClick() { analyticsEventHandler.send(WalletSettingsAnalyticEvents.ButtonBackup()) - router.push(AppRoute.WalletBackup(params.userWalletId)) + router.push( + AppRoute.WalletBackup( + userWalletId = params.userWalletId, + isColdWalletOptionShown = false, + ), + ) } private fun onCardSettingsClick() { router.push(AppRoute.CardSettings(params.userWalletId)) } - private fun showMakeBackupAtFirstAlertBS( - action: WalletSettingsAnalyticEvents.NoticeBackupFirst.Action, - isUpgradeFlow: Boolean, - ) { + private fun showMakeBackupAtFirstAlertBS() { analyticsEventHandler.send( event = WalletSettingsAnalyticEvents.NoticeBackupFirst( source = AnalyticsParam.ScreensSources.WalletSettings.value, - action = action, + action = WalletSettingsAnalyticEvents.NoticeBackupFirst.Action.AccessCode, ), ) val message = bottomSheetMessage { @@ -438,14 +430,10 @@ internal class WalletSettingsModel @Inject constructor( router.push( AppRoute.CreateWalletBackup( userWalletId = params.userWalletId, - isUpgradeFlow = isUpgradeFlow, + isUpgradeFlow = false, shouldSetAccessCode = true, analyticsSource = AnalyticsParam.ScreensSources.WalletSettings.value, - analyticsAction = if (isUpgradeFlow) { - RecoveryPhraseScreenAction.Upgrade.value - } else { - RecoveryPhraseScreenAction.AccessCode.value - }, + analyticsAction = RecoveryPhraseScreenAction.AccessCode.value, ), ) closeBs() diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt index 7f9cb9d418..6590758780 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/ui/WalletSettingsScreen.kt @@ -42,7 +42,6 @@ import com.tangem.core.ui.components.notifications.Notification import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -153,10 +152,6 @@ private fun Content(state: WalletSettingsUM, modifier: Modifier = Modifier) { modifier = itemModifier, model = item, ) - is WalletSettingsItemUM.UpgradeWallet -> UpgradeWalletBlock( - modifier = itemModifier, - model = item, - ) is WalletSettingsAccountsUM.Header -> AccountsHeader(item, itemModifier) is WalletSettingsAccountsUM.Account -> AccountItem( model = item, @@ -211,35 +206,54 @@ private fun CardBlock(model: WalletSettingsItemUM.CardBlock, modifier: Modifier enabled = model.isEnabled, onClick = model.onClick, ) { - Row( - modifier = Modifier.padding(all = TangemTheme.dimens.spacing12), - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), - verticalAlignment = Alignment.CenterVertically, - ) { - CardImage(model.imageState) - Column(modifier = Modifier.weight(1f)) { - Text( - text = model.title.resolveReference(), - color = TangemTheme.colors.text.tertiary, - style = TangemTheme.typography.caption2, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = model.text.resolveReference(), - color = TangemTheme.colors.text.primary1, - style = TangemTheme.typography.subtitle1, - overflow = TextOverflow.Ellipsis, - maxLines = 1, + Column { + Row( + modifier = Modifier.padding(all = TangemTheme.dimens.spacing12), + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + verticalAlignment = Alignment.CenterVertically, + ) { + CardImage(model.imageState) + Column(modifier = Modifier.weight(1f)) { + Text( + text = model.title.resolveReference(), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption2, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = model.text.resolveReference(), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + } + SecondarySmallButton( + config = SmallButtonConfig( + isEnabled = model.isEnabled, + text = resourceReference(R.string.common_rename), + onClick = model.onClick, + ), ) } - SecondarySmallButton( - config = SmallButtonConfig( - isEnabled = model.isEnabled, - text = resourceReference(R.string.common_rename), - onClick = model.onClick, - ), - ) + + if (model.additionalBlock != null) { + Column { + HorizontalDivider( + thickness = TangemTheme.dimens.size0_5, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.spacing12), + color = TangemTheme.colors.stroke.primary, + ) + + BlockItem( + modifier = Modifier.fillMaxWidth(), + model = model.additionalBlock, + ) + } + } } } } @@ -272,21 +286,6 @@ private fun SwitchBlock(model: WalletSettingsItemUM.WithSwitch, modifier: Modifi } } -@Composable -private fun UpgradeWalletBlock(model: WalletSettingsItemUM.UpgradeWallet, modifier: Modifier = Modifier) { - Notification( - config = NotificationConfig( - title = model.title, - subtitle = model.description, - iconResId = R.drawable.ic_hardware_backup_36, - iconSize = 36.dp, - onClick = model.onClick, - shouldShowArrowIcon = false, - ), - modifier = modifier, - ) -} - @Composable private fun NotificationAlertBlock(model: WalletSettingsItemUM.NotificationPermission, modifier: Modifier = Modifier) { Notification( @@ -480,20 +479,4 @@ private fun Preview_WalletSettingsScreen() { PreviewWalletSettingsComponent().Content(modifier = Modifier.fillMaxSize()) } } - -@Composable -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun Preview_WalletSettingsScreen1() { - TangemThemePreview { - UpgradeWalletBlock( - model = WalletSettingsItemUM.UpgradeWallet( - id = "upgrade_wallet", - title = stringReference("Upgrade wallet with a hardware backup"), - description = stringReference("Keep your crypto safe with Tangem’s best-in-class hardware wallet."), - onClick = {}, - ), - ) - } -} // endregion Preview \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt index 86b1637320..a103dee2de 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt @@ -38,17 +38,10 @@ internal class ItemsBuilder @Inject constructor() { onReferralClick: () -> Unit, onManageTokensClick: () -> Unit, onAccessCodeClick: () -> Unit, - onUpgradeWalletClick: () -> Unit, onBackupClick: () -> Unit, onCardSettingsClick: () -> Unit, ): PersistentList = persistentListOf() .add(cardItem) - .addAll( - buildUpgradeWalletItem( - userWallet = userWallet, - onUpgradeWalletClick = onUpgradeWalletClick, - ), - ) .addAll(buildAccessCodeItem(userWallet, onAccessCodeClick)) .addAll(accountsUM) .add( @@ -116,21 +109,6 @@ internal class ItemsBuilder @Inject constructor() { onCheckedChange = onCheckedNFTChange, ) - private fun buildUpgradeWalletItem( - userWallet: UserWallet, - onUpgradeWalletClick: () -> Unit, - ): List = when (userWallet) { - is UserWallet.Cold -> emptyList() - is UserWallet.Hot -> listOf( - WalletSettingsItemUM.UpgradeWallet( - id = "upgrade_wallet", - title = resourceReference(id = R.string.hw_upgrade_to_cold_banner_title), - description = resourceReference(id = R.string.hw_upgrade_to_cold_banner_description), - onClick = onUpgradeWalletClick, - ), - ) - } - private fun buildNotificationsPermissionItem() = WalletSettingsItemUM.NotificationPermission( id = "notifications_permission", title = resourceReference(id = R.string.transaction_notifications_warning_title), diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/WalletCardItemDelegate.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/WalletCardItemDelegate.kt index 18d1546028..8162044ed2 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/WalletCardItemDelegate.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/WalletCardItemDelegate.kt @@ -2,6 +2,7 @@ package com.tangem.feature.walletsettings.utils import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate +import com.tangem.core.ui.components.block.model.BlockUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.models.wallet.UserWallet @@ -24,6 +25,7 @@ internal class WalletCardItemDelegate @AssistedInject constructor( private val getShouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase, private val walletImageFetcher: UserWalletImageFetcher, @Assisted private val dialogNavigation: SlotNavigation, + @Assisted private val onUpgradeHotWalletClick: () -> Unit, private val hotWalletFeatureToggles: HotWalletFeatureToggles, ) { @@ -43,10 +45,20 @@ internal class WalletCardItemDelegate @AssistedInject constructor( isEnabled = isRenameAvailable, onClick = { openRenameWalletDialog(wallet) }, imageState = imageState, + additionalBlock = buildUpgradeToHardwareWalletBlockOrNull(wallet), ) }, ) + private fun buildUpgradeToHardwareWalletBlockOrNull(wallet: UserWallet): BlockUM? { + return BlockUM( + text = resourceReference(R.string.upgrade_to_hardware_wallet_button_title), + iconRes = null, + onClick = onUpgradeHotWalletClick, + accentType = BlockUM.AccentType.ACCENT, + ).takeIf { wallet is UserWallet.Hot } + } + private fun openRenameWalletDialog(userWallet: UserWallet) { val config = DialogConfig.RenameWallet( userWalletId = userWallet.walletId, @@ -57,6 +69,9 @@ internal class WalletCardItemDelegate @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(dialogNavigation: SlotNavigation): WalletCardItemDelegate + fun create( + dialogNavigation: SlotNavigation, + onUpgradeHotWalletClick: () -> Unit, + ): WalletCardItemDelegate } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index 8d1c6bd283..ed19a09083 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -477,7 +477,16 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( override fun onFinishWalletActivationClick(isBackupExists: Boolean) { analyticsEventHandler.send(MainScreen.ButtonFinalizeActivation()) val userWalletId = stateHolder.getSelectedWalletId() - appRouter.push(WalletActivation(userWalletId, isBackupExists)) + val route = if (isBackupExists) { + WalletActivation(userWalletId = userWalletId, isBackupExists = true) + } else { + WalletBackup( + userWalletId = userWalletId, + isColdWalletOptionShown = true, + ) + } + + appRouter.push(route) } override fun onAllowPermissions() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 033a18e216..1eb6944175 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.AppsFlyerOnlyEvent +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent import com.tangem.core.analytics.models.OneTimeAnalyticsEvent import com.tangem.domain.models.wallet.UserWalletId @@ -18,16 +18,11 @@ sealed class WalletScreenAnalyticsEvent { event = "Topped up", params = mapOf(AnalyticsParam.CURRENCY to walletType.value), ), - OneTimeAnalyticsEvent { + OneTimeAnalyticsEvent, AppsFlyerIncludedEvent { override val oneTimeEventId: String = id + userWalletId.stringValue } - class AppsFlyerWalletFunded(userWalletId: UserWalletId) : Basic(event = "wallet_funded"), - AppsFlyerOnlyEvent, OneTimeAnalyticsEvent { - override val oneTimeEventId: String = id + userWalletId.stringValue - } - class CardWasScanned(source: AnalyticsParam.ScreensSources) : Basic( event = "Card Was Scanned", params = mapOf( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index c3de527e77..5cfa978a2a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -208,7 +208,6 @@ internal class TokenListAnalyticsSender @Inject constructor( } analyticsEventHandler.send(Basic.WalletToppedUp(userWallet.walletId, walletType)) - analyticsEventHandler.send(Basic.AppsFlyerWalletFunded(userWallet.walletId)) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsSingleEventSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsSingleEventSender.kt index 69b492660a..4b4227a657 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsSingleEventSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsSingleEventSender.kt @@ -1,6 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils -import com.tangem.common.routing.AppRoute.WalletActivation +import com.tangem.common.routing.AppRoute.WalletBackup import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender @@ -93,7 +93,12 @@ internal class WalletWarningsSingleEventSender @Inject constructor( primaryButton { text = resourceReference(R.string.hw_activation_need_backup) onClick { - router.push(WalletActivation(userWallet.walletId, userWallet.backedUp)) + router.push( + WalletBackup( + userWalletId = userWalletId, + isColdWalletOptionShown = true, + ), + ) closeBs() } } diff --git a/features/welcome/impl/build.gradle.kts b/features/welcome/impl/build.gradle.kts index 7557118256..09713985c8 100644 --- a/features/welcome/impl/build.gradle.kts +++ b/features/welcome/impl/build.gradle.kts @@ -14,6 +14,7 @@ android { dependencies { implementation(projects.features.welcome.api) implementation(projects.features.wallet.api) + implementation(projects.features.hotWallet.api) /** Core */ implementation(projects.core.configToggles) diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt index b5b89566f2..4179ffefe8 100644 --- a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -12,14 +12,22 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.card.ScanCardProcessor import com.tangem.domain.common.wallets.UserWalletsListRepository +import com.tangem.domain.common.wallets.error.SaveWalletError import com.tangem.domain.common.wallets.error.UnlockWalletError import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.settings.CanUseBiometryUseCase +import com.tangem.domain.wallets.builder.ColdUserWalletBuilder import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.NonBiometricUnlockWalletUseCase +import com.tangem.domain.wallets.usecase.SaveWalletUseCase +import com.tangem.features.hotwallet.HotWalletFeatureToggles import com.tangem.features.wallet.utils.UserWalletsFetcher import com.tangem.features.welcome.impl.ui.state.WelcomeUM import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -40,10 +48,15 @@ internal class WelcomeModel @Inject constructor( private val userWalletsListRepository: UserWalletsListRepository, private val nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase, private val canUseBiometryUseCase: CanUseBiometryUseCase, + private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory, + private val saveWalletUseCase: SaveWalletUseCase, private val walletsRepository: WalletsRepository, private val trackingContextProxy: TrackingContextProxy, private val analyticsEventHandler: AnalyticsEventHandler, userWalletsFetcherFactory: UserWalletsFetcher.Factory, + private val hotWalletFeatureToggles: HotWalletFeatureToggles, + private val scanCardProcessor: ScanCardProcessor, + private val messageSender: UiMessageSender, ) : Model() { val uiState: StateFlow @@ -168,7 +181,50 @@ internal class WelcomeModel @Inject constructor( private fun addWalletClick() { analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.SignIn)) - router.push(AppRoute.CreateWalletSelection) + if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled) { + scanCard() + } else { + router.push(AppRoute.CreateWalletSelection) + } + } + + private fun scanCard() { + modelScope.launch { + scanCardProcessor.scan( + analyticsSource = AnalyticsParam.ScreensSources.SignIn, + onWalletNotCreated = {}, + disclaimerWillShow = { router.pop() }, + onSuccess = { scanResponse -> + val userWallet = + coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build() ?: return@scan + saveWalletUseCase.invoke(userWallet) + .onLeft { error -> + if (error is SaveWalletError.WalletAlreadySaved) { + userWalletsListRepository.unlock( + userWallet.walletId, + unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), + ).onRight { + userWalletsListRepository.select(userWallet.walletId) + router.replaceAll(AppRoute.Wallet) + } + } + } + .onRight { + router.replaceAll(AppRoute.Wallet) + } + }, + onCancel = {}, + onFailure = { tangemError -> + if (!tangemError.silent) { + val message = tangemError.messageResId + ?.let(::resourceReference) + ?: stringReference(tangemError.customMessage) + + messageSender.send(SnackbarMessage(message)) + } + }, + ) + } } private suspend fun onlyOneHotWalletWithAccessCode(): Boolean { diff --git a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt index 6d5f950820..02f6ba56aa 100644 --- a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt +++ b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt @@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_DESCRIPTION import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent sealed class YieldSupplyAnalytics( event: String, @@ -20,7 +21,7 @@ sealed class YieldSupplyAnalytics( TOKEN_PARAM to token, BLOCKCHAIN to blockchain, ), - ) + ), AppsFlyerIncludedEvent data class StartEarningScreen( val token: String, @@ -31,7 +32,7 @@ sealed class YieldSupplyAnalytics( TOKEN_PARAM to token, BLOCKCHAIN to blockchain, ), - ) + ), AppsFlyerIncludedEvent data class StopEarningScreen( val token: String, @@ -42,7 +43,7 @@ sealed class YieldSupplyAnalytics( TOKEN_PARAM to token, BLOCKCHAIN to blockchain, ), - ) + ), AppsFlyerIncludedEvent data class ButtonStartEarning( val token: String, @@ -97,7 +98,7 @@ sealed class YieldSupplyAnalytics( TOKEN_PARAM to token, BLOCKCHAIN to blockchain, ), - ) + ), AppsFlyerIncludedEvent data class FundsEarned( val token: String, @@ -108,7 +109,7 @@ sealed class YieldSupplyAnalytics( TOKEN_PARAM to token, BLOCKCHAIN to blockchain, ), - ) + ), AppsFlyerIncludedEvent data class FundsWithdrawn( val token: String, @@ -119,7 +120,7 @@ sealed class YieldSupplyAnalytics( TOKEN_PARAM to token, BLOCKCHAIN to blockchain, ), - ) + ), AppsFlyerIncludedEvent data class EarnedFundsInfo( val token: String, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 02b196315c..7e3eb32287 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -47,6 +47,7 @@ compose-lifecycle-runtime = "2.7.0" # region Other libraries appsflyer = "6.17.3" +appsflyer-oaid = "6.12.3" amplitude = "2.36.1" amplitude-experiment = "1.13.1" armadillo = "0.9.0" @@ -238,6 +239,7 @@ test-orchestrator = { module = "androidx.test:orchestrator", version.ref = "orch # region Other appsflyer = { module = "com.appsflyer:af-android-sdk", version.ref = "appsflyer" } +appsflyer-oaid = { module = "com.appsflyer:oaid", version.ref = "appsflyer-oaid" } amplitude = { module = "com.amplitude:android-sdk", version.ref = "amplitude" } amplitude-experiment = { module = "com.amplitude:experiment-android-client", version.ref = "amplitude-experiment" } armadillo = { module = "at.favre.lib:armadillo", version.ref = "armadillo" } diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index be628ea2c1..d9fd4bc8f9 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1371" +tangemBlockchainSdk = "develop-1383" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-573" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^