diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 36145b0415..65f7822aff 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -188,6 +188,8 @@ dependencies { implementation(projects.features.stories.impl) implementation(projects.features.txhistory.api) implementation(projects.features.txhistory.impl) + implementation(projects.features.askBiometry.api) + implementation(projects.features.askBiometry.impl) implementation(projects.features.nft.api) implementation(projects.features.nft.impl) 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 a9a8c23e01..a7304f434c 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 @@ -58,5 +58,9 @@ { "name": "TX_HISTORY_REFACTORING_ENABLED", "version": "undefined" + }, + { + "name": "ASK_BIOMETRY_REFACTORING_ENABLED", + "version": "undefined" } ] diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 6edea905ea..003034b671 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -241,6 +241,7 @@ Dieser Mechanismus schützt vor Annäherungsangriffen auf eine Karte oder einen Ring. Er erzwingt eine Verzögerung zwischen dem Empfang und der Ausführung eines Befehls. Passcode Bevor du einen Befehl ausführst, der eine Änderung des Kartenstatus zur Folge hat, musst du den Passcode eingeben. + NFT Empfehlungsprogramm Flippe den Bildschirm deines Geräts nach unten, um Salden schnell ein- und auszublenden %s Hasch @@ -536,6 +537,7 @@ Möchtest du den Aktivierungsprozess abbrechen? Erste Schritte Für die Karte oder Ring, die du hinzufügen möchtest, wurde bereits eine andere Wallets erstellt. Wenn du Guthaben auf dieser Wallets hast, hebe es bitte ab, setze diese Karte oder Ring zurück und füge sie als Backup hinzu. + Sicher deine Wallet Backups anlegen Lese mehr über die Seed-Phrase @@ -668,6 +670,7 @@ Nutzung biometrischer Daten zulassen Bei Interaktionen mit deiner Wallet werden anstelle des Zugangscodes biometrische Daten abgefragt Zugangscode + Nicht zulassen Es sieht so aus, als ob du die biometrische Authentifizierung deaktiviert hast. Diese ist notwendig, um Wallets zu speichern Biometrische Autorisierung aktivieren Möchtest du Biometrie nutzen? diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index ada8a5bd4d..5b203d5b9a 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -241,6 +241,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. + NFT Programa de referidos Gire la pantalla de su dispositivo hacia abajo para ocultar y mostrar rápidamente los saldos %s hashes @@ -534,6 +535,7 @@ ¿Quiere salir del proceso de activación? Inicializando Ya se ha creado otra billetera en la tarjeta que está intentando agregar. Si tiene fondos en esta billetera, por favor retírelos y luego reinicie esta tarjeta y agréguela como backup. + Guardar su billetera Creando un backup Leer más sobre seed phrase @@ -666,6 +668,7 @@ Permitir el uso de biometría Se solicitará la biometría en lugar del código de acceso para interactuar con su billetera Código de acceso + No autorizar Parece que tiene la autenticación biométrica deshabilitada, es necesaria para guardar billeteras Activar la autorización biométrica ¿Le gustaría usar la biometría? diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 529de205c8..05074ae06e 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -241,6 +241,7 @@ Ce mécanisme protège contre les attaques sans contact sur la carte. Il y a un délai entre la réception et l\'exécution de la commande. Après la première transaction signée, ce téléphone sera associé à la carte et les transactions seront signées immédiatement. Mot de passe Avant d\'exécuter une commande qui modifie l\'état de la carte, vous devrez entrer un mot de passe. + NFT Programme de parrainage Retournez l\'écran de votre appareil vers le bas pour masquer et afficher rapidement les soldes %s hashes @@ -536,6 +537,7 @@ Voulez-vous quitter le processus d\'activation ? Initialisation en cours Un autre portefeuille a déjà été créé sur la carte que vous essayez d\'ajouter. Si vous avez des fonds dans ce portefeuille, veuillez les retirer, puis réinitialiser cette carte et l\'ajouter comme sauvegarde. + Enregistrer votre portefeuille Sauvegarde en cours En savoir plus sur les seed phrases @@ -668,6 +670,7 @@ Autoriser l\'utilisation de la biométrie La biométrie sera demandée à la place du code d\'accès pour les interactions avec votre portefeuille Code d\'accès + Ne pas autoriser Il semble que vous ayez désactivé l\'authentification biométrique, elle est nécessaire pour sauvegarder les portefeuilles Activer l\'autorisation biométrique Souhaitez-vous utiliser la biométrie ? diff --git a/core/res/src/main/res/values-it/strings.xml b/core/res/src/main/res/values-it/strings.xml index 900ef94aa5..9b25886a73 100644 --- a/core/res/src/main/res/values-it/strings.xml +++ b/core/res/src/main/res/values-it/strings.xml @@ -19,6 +19,7 @@ Questo meccanismo protegge dagli avvicinamenti senza contatto sulla carta. Attiva un ritardo tra la ricezione e l\'esecuzione di un comando. Dopo la prima transazione firmata, questo telefono verrà associato alla carta e le transazioni verranno firmate immediatamente. Password Dovrai inserire una password prima di eseguire qualsiasi comando che modifichi lo stato della carta. + NFT Hash %s ID carta Valuta dell\'applicazione diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index aedabcb589..6abf715762 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -238,6 +238,7 @@ このメカニズムは、カードまたはリングに対する近接攻撃から保護します。コマンドの受信と実行の間に遅延を強制します。 パスコード カードの状態の変更を伴うコマンドを実行する前に、パスコードを入力する必要があります。 + NFT 紹介プログラム デバイスの画面を下に向けると、残高をすばやく非表示にしたり表示したりできます。 %sハッシュ @@ -530,6 +531,7 @@ アクティベーション処理を途中で終了しますか? スタート 追加しようとしているカードには、すでに別のウォレットが作成されています。このウォレットに資金がある場合は、それを引き出してからこのカードをリセットし、バックアップとして追加してください。 + ウォレットを保存する バックアップの作成 シードフレーズについてもっと読む @@ -658,6 +660,7 @@ 生体認証の使用を許可する ウォレットの操作には、アクセスコードの代わりに生体認証が要求されます。 アクセスコード + 許可しない 生体認証が無効になっているようです。ウォレットを保存する必要があります。 生体認証を有効にする 生体認証を使用しますか? @@ -768,6 +771,7 @@ %s 推定利益 市場評価 指標 + %1$sネットワークルールによれば、 %2$sからの請求が可能です。以下の金額は、ステーキング解除時にアカウントに入金されます。 最低要件 報酬はありません 請求中の報酬 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 8793740014..33b0c3b4a9 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -247,6 +247,7 @@ Этот механизм защищает карту или кольцо от бесконтактных атак. Между сканированием и выполнением команды будет добавлена задержка. Пароль Перед выполнением любой команды, влекущей за собой изменение состояния карты, вам необходимо будет ввести пароль. + NFT Реферальная программа Переверните экран вашего устройства вниз, чтобы быстро скрыть и отобразить балансы %s хэшей @@ -546,6 +547,7 @@ Вы хотите выйти из процесса активации? Подготовка Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Если на нем есть средства, пожалуйста сначала выведите их, а затем сделайте сброс до заводских настроек и используйте как резервную. + Сохраните ваш кошелек Резервная копия Прочитать о seed-фразе @@ -639,7 +641,7 @@ %1$s (%2$s) в сети %3$s Использование другой сети может привести к утрате средств. Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств. - Отправляйте только %1$s в сети %2$s + Отправляйте только %1$s в сети %2$s Переводите средства с любого кошелька или биржи Участвовать Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже. @@ -686,6 +688,7 @@ Использовать биометрию Для операций с вашим кошельком будет запрашиваться биометрия вместо кода доступа карты Код доступа + Не использовать Похоже, что у вас отключена биометрическая аутентификация, она необходима для сохранения кошельков Включите биометрическую аутентификацию Вы хотите использовать биометрию? diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 91701bc5c1..214896573f 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -247,6 +247,7 @@ Цей механізм захищає картку або кільце від безконтактних атак. Між скануванням картки та виконанням команди буде додана затримка. Пароль Перед виконанням будь-якої команди, що тягне за собою зміну стану картки, вам необхідно буде ввести пароль. + NFT Реферальна програма Переверніть екран пристрою вниз, щоб швидко приховати та відобразити баланси %s хешів @@ -546,6 +547,7 @@ Ви хочете вийти з процесу активації? Підготовка На картці, яку ви намагаєтеся додати, вже створено інший гаманець. Якщо у вас є кошти на цьому гаманці, будь ласка, виведіть їх, а потім скиньте цю картку до заводських налаштувань і додайте її як резервну. + Збережіть ваш гаманець Резервна копія Дізнатися більше про seed-фразу @@ -686,6 +688,7 @@ Використовувати біометрію Для взаємодії з гаманцем будуть запитуватися біометричні дані замість коду доступу Код доступу + Не дозволяти Схоже, що у вас відключена біометрична автентифікація, вона необхідна для збереження гаманців Увімкніть біометричну автентифікацію Бажаєте використовувати біометрію? diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index aaa494dbeb..d4ab1bc4e2 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -108,6 +108,7 @@ 此機制可防止對卡進行鄰近攻擊。它將強制執行命令的接收和執行之間的延遲。 密碼 在執行任何需要更改卡狀態的命令之前,您必須輸入密碼 + NFT 推薦計畫 %s hashes 卡號 @@ -176,6 +177,7 @@ 這此情況,您必須要重新開始 您想要離開啟用程序嗎? 開始 + 儲存你的錢包 創建備份 閱讀更多關於助記詞的訊息 @@ -241,6 +243,7 @@ 允許使用生物辨識 將要求生物識別而不是訪問密碼來與您的錢包進行交互 訪問密碼 + 不允許 您好像禁用了生物識別,有必要保存錢包 啟用生物識別授權 您想使用生物識別技術嗎? diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 9b4f9524be..68238443cf 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -537,6 +537,7 @@ Do you want to exit the activation process? Getting started Another wallet has already been created on the card you\'re trying to add. If you have funds in this wallet, please withdraw it and then reset this card and add it as a backup. + Save your wallet Creating a backup Read more about seed phrase @@ -669,6 +670,7 @@ Allow to use biometrics Biometrics will be requested instead of the access code for interactions with your wallet Access code + Don\'t allow It looks like you have biometric authentication disabled, it is necessary to save wallets Enable biometric authorization Would you like to use biometrics? @@ -779,6 +781,7 @@ %s est. profit Market rating Metrics + According to %1$s network rules, claims are possible from %2$s. Amounts below will be credited to your account upon unstaking. Minimum Requirement No rewards available Reward claiming diff --git a/core/ui/src/main/res/drawable/ic_fingerprint_24.xml b/core/ui/src/main/res/drawable/ic_fingerprint_24.xml new file mode 100644 index 0000000000..df5bdf4868 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_fingerprint_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/features/ask-biometry/api/.gitignore b/features/ask-biometry/api/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/ask-biometry/api/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/ask-biometry/api/build.gradle.kts b/features/ask-biometry/api/build.gradle.kts new file mode 100644 index 0000000000..4e2e227056 --- /dev/null +++ b/features/ask-biometry/api/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.askbiometry" +} + +dependencies { + /* Project - Core */ + implementation(projects.core.decompose) + implementation(projects.core.ui) +} \ No newline at end of file diff --git a/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryComponent.kt b/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryComponent.kt new file mode 100644 index 0000000000..c01180af46 --- /dev/null +++ b/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryComponent.kt @@ -0,0 +1,20 @@ +package com.tangem.features.askbiometry + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface AskBiometryComponent : ComposableContentComponent, ComposableBottomSheetComponent { + + data class Params( + val bottomSheetVariant: Boolean, + val modelCallbacks: ModelCallbacks, + ) + + interface ModelCallbacks { + fun onAllowed() + fun onDenied() + } + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryFeatureToggles.kt b/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryFeatureToggles.kt new file mode 100644 index 0000000000..22425c768e --- /dev/null +++ b/features/ask-biometry/api/src/main/kotlin/com/tangem/features/askbiometry/AskBiometryFeatureToggles.kt @@ -0,0 +1,5 @@ +package com.tangem.features.askbiometry + +interface AskBiometryFeatureToggles { + val isEnabled: Boolean +} \ No newline at end of file diff --git a/features/ask-biometry/impl/.gitignore b/features/ask-biometry/impl/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/features/ask-biometry/impl/.gitignore @@ -0,0 +1 @@ +/build diff --git a/features/ask-biometry/impl/build.gradle.kts b/features/ask-biometry/impl/build.gradle.kts new file mode 100644 index 0000000000..c9d6bc3fac --- /dev/null +++ b/features/ask-biometry/impl/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.kotlin.serialization) + alias(deps.plugins.hilt.android) + id("configuration") +} + +android { + namespace = "com.tangem.features.askbiometry.impl" +} + +dependencies { + api(projects.features.askBiometry.api) + + /** Core modules */ + implementation(projects.core.ui) + implementation(projects.core.res) + implementation(projects.core.decompose) + implementation(projects.core.analytics) + implementation(projects.core.analytics.models) + implementation(projects.core.configToggles) + implementation(projects.core.navigation) + + /** Domain */ + implementation(projects.domain.wallets) + implementation(projects.domain.wallets.models) + implementation(projects.domain.settings) + implementation(projects.domain.card) + + /** Compose libraries */ + implementation(deps.compose.material3) + implementation(deps.compose.animation) + implementation(deps.compose.foundation) + implementation(deps.compose.ui) + implementation(deps.compose.ui.tooling) + + /** Tangem libraries */ + implementation(projects.libs.tangemSdkApi) + implementation(tangemDeps.card.core) + implementation(tangemDeps.card.android) { + exclude(module = "joda-time") + } + + /** Other */ + implementation(deps.timber) + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryComponent.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryComponent.kt new file mode 100644 index 0000000000..3f93ca138e --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryComponent.kt @@ -0,0 +1,63 @@ +package com.tangem.features.askbiometry.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.features.askbiometry.AskBiometryComponent +import com.tangem.features.askbiometry.impl.model.AskBiometryModel +import com.tangem.features.askbiometry.impl.ui.AskBiometry +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultAskBiometryComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: AskBiometryComponent.Params, +) : AskBiometryComponent, AppComponentContext by appComponentContext { + + private val model: AskBiometryModel = getOrCreateModel(params) + + override fun dismiss() = model.dismiss() + + @Composable + override fun BottomSheet() { + val state by model.uiState.collectAsStateWithLifecycle() + val bsConfig = remember(this) { + TangemBottomSheetConfig( + isShown = true, + onDismissRequest = ::dismiss, + content = TangemBottomSheetConfigContent.Empty, + ) + } + + TangemBottomSheet( + config = bsConfig, + content = { AskBiometry(state) }, + ) + } + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + AskBiometry( + modifier = modifier, + state = state, + ) + } + + @AssistedFactory + interface Factory : AskBiometryComponent.Factory { + override fun create( + context: AppComponentContext, + params: AskBiometryComponent.Params, + ): DefaultAskBiometryComponent + } +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryFeatureToggles.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryFeatureToggles.kt new file mode 100644 index 0000000000..e2be13dfb9 --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/DefaultAskBiometryFeatureToggles.kt @@ -0,0 +1,12 @@ +package com.tangem.features.askbiometry.impl + +import com.tangem.core.configtoggle.feature.FeatureTogglesManager +import com.tangem.features.askbiometry.AskBiometryFeatureToggles +import javax.inject.Inject + +class DefaultAskBiometryFeatureToggles @Inject constructor( + private val featureTogglesManager: FeatureTogglesManager, +) : AskBiometryFeatureToggles { + override val isEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("ASK_BIOMETRY_REFACTORING_ENABLED") +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/di/FeatureModule.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/di/FeatureModule.kt new file mode 100644 index 0000000000..e9e8bc3bc1 --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/di/FeatureModule.kt @@ -0,0 +1,36 @@ +package com.tangem.features.askbiometry.impl.di + +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.askbiometry.AskBiometryComponent +import com.tangem.features.askbiometry.AskBiometryFeatureToggles +import com.tangem.features.askbiometry.impl.DefaultAskBiometryComponent +import com.tangem.features.askbiometry.impl.DefaultAskBiometryFeatureToggles +import com.tangem.features.askbiometry.impl.model.AskBiometryModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(SingletonComponent::class) +internal interface FeatureModule { + + @Binds + fun bindFeatureToggles(impl: DefaultAskBiometryFeatureToggles): AskBiometryFeatureToggles + + @Binds + fun bindComponentFactory(impl: DefaultAskBiometryComponent.Factory): AskBiometryComponent.Factory +} + +@Module +@InstallIn(ModelComponent::class) +internal interface ModelModule { + + @Binds + @IntoMap + @ClassKey(AskBiometryModel::class) + fun provideModel(model: AskBiometryModel): Model +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/model/AskBiometryModel.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/model/AskBiometryModel.kt new file mode 100644 index 0000000000..a72832828f --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/model/AskBiometryModel.kt @@ -0,0 +1,130 @@ +package com.tangem.features.askbiometry.impl.model + +import androidx.compose.runtime.Stable +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.ui.UiMessageSender +import com.tangem.core.navigation.settings.SettingsManager +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.DialogMessage +import com.tangem.core.ui.message.EventMessageAction +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.settings.SetSaveWalletScreenShownUseCase +import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.wallets.legacy.UserWalletsListManager +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.repository.WalletsRepository +import com.tangem.features.askbiometry.AskBiometryComponent +import com.tangem.features.askbiometry.impl.ui.state.AskBiometryUM +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +@Stable +@ModelScoped +internal class AskBiometryModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + paramsContainer: ParamsContainer, + private val setSaveWalletScreenShownUseCase: SetSaveWalletScreenShownUseCase, + private val settingsRepository: SettingsRepository, + private val tangemSdkManager: TangemSdkManager, + private val userWalletsListManager: UserWalletsListManager, + private val walletsRepository: WalletsRepository, + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val settingsManager: SettingsManager, + private val uiMessageSender: UiMessageSender, +) : Model() { + + private val params = paramsContainer.require() + + private val _uiState = MutableStateFlow( + AskBiometryUM( + bottomSheetVariant = params.bottomSheetVariant, + onAllowClick = ::onAllowClick, + onDontAllowClick = ::dontAllow, + ), + ) + val uiState = _uiState.asStateFlow() + + init { + modelScope.launch { + setSaveWalletScreenShownUseCase() + } + } + + private fun onAllowClick() { + _uiState.update { it.copy(showProgress = true) } + allowToUseBiometrics() + } + + private fun dontAllow() { + params.modelCallbacks.onDenied() + } + + fun dismiss() { + dontAllow() + } + + private fun allowToUseBiometrics() { + modelScope.launch { + if (tangemSdkManager.checkNeedEnrollBiometrics()) { + showEnrollBiometricsDialog() + return@launch + } + + /* + + * because it will be automatically saved on UserWalletsListManager switch + */ + val selectedUserWallet = userWalletsListManager.selectedUserWalletSync ?: run { + Timber.e("Unable to save user wallet") + uiMessageSender.send( + SnackbarMessage(stringReference("No selected user wallet")), + ) + + return@launch + } + + handleSuccessAllowing(selectedUserWallet) + } + } + + private suspend fun handleSuccessAllowing(userWallet: UserWallet) { + walletsRepository.saveShouldSaveUserWallets(item = true) + settingsRepository.setShouldSaveAccessCodes(value = true) + + cardSdkConfigRepository.setAccessCodeRequestPolicy( + isBiometricsRequestPolicy = userWallet.hasAccessCode, + ) + + params.modelCallbacks.onAllowed() + } + + private fun showEnrollBiometricsDialog() { + uiMessageSender.send( + DialogMessage( + message = resourceReference(R.string.save_user_wallet_agreement_enroll_biometrics_description), + title = resourceReference(R.string.save_user_wallet_agreement_enroll_biometrics_title), + firstAction = EventMessageAction( + title = resourceReference(R.string.common_enable), + onClick = { settingsManager.openBiometricSettings() }, + ), + secondAction = EventMessageAction( + title = resourceReference(R.string.common_cancel), + onClick = {}, + ), + dismissOnFirstAction = true, + ), + ) + } +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/AskBiometry.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/AskBiometry.kt new file mode 100644 index 0000000000..09521a2b56 --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/AskBiometry.kt @@ -0,0 +1,209 @@ +package com.tangem.features.askbiometry.impl.ui + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.atoms.Hand +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.askbiometry.impl.ui.state.AskBiometryUM +import com.tangem.features.askbiometry.impl.R + +@Composable +internal fun AskBiometry(state: AskBiometryUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column( + modifier = Modifier.weight(1f), + ) { + if (state.bottomSheetVariant) { + Header(onCloseClick = state.onDontAllowClick) + } + + SpacerH32() + + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + ) { + Title(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing22)) + SpacerH32() + Description( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing34) + .fillMaxWidth(), + ) + } + } + Footer(state = state) + SpacerH16() + } +} + +@Composable +private fun Header(onCloseClick: () -> Unit) { + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), + ) { + Hand() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + IconButton( + modifier = Modifier.size(TangemTheme.dimens.size32), + onClick = onCloseClick, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = painterResource(id = R.drawable.ic_close_24), + tint = TangemTheme.colors.icon.secondary, + contentDescription = stringResourceSafe(id = R.string.common_cancel), + ) + } + SpacerW8() + } + } +} + +@Composable +private fun Title(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing32), + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size56), + painter = painterResource(id = R.drawable.ic_fingerprint_24), + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + Text( + text = stringResourceSafe(id = R.string.save_user_wallet_agreement_header_biometrics), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + } +} + +@Composable +private fun Description(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing24), + horizontalAlignment = Alignment.Start, + ) { + DescriptionItem( + iconPainter = painterResource(id = R.drawable.ic_face_recognition_24), + title = stringResourceSafe(id = R.string.save_user_wallet_agreement_access_title), + description = stringResourceSafe(id = R.string.save_user_wallet_agreement_access_description), + ) + DescriptionItem( + iconPainter = painterResource(id = R.drawable.ic_lock_24), + title = stringResourceSafe(id = R.string.save_user_wallet_agreement_code_title), + description = stringResourceSafe(id = R.string.save_user_wallet_agreement_code_description_biometrics), + ) + } +} + +@Composable +private fun Footer(state: AskBiometryUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + showProgress = state.showProgress, + text = stringResourceSafe(id = R.string.save_user_wallet_agreement_allow_biometrics), + onClick = state.onAllowClick, + ) + + if (state.bottomSheetVariant.not()) { + SpacerH12() + + SecondaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe(id = R.string.save_user_wallet_agreement_dont_allow), + onClick = state.onDontAllowClick, + ) + } + + SpacerH16() + + Text( + modifier = Modifier.fillMaxWidth(fraction = .7f), + text = stringResourceSafe(R.string.save_user_wallet_agreement_notice), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + ) + } +} + +@Composable +private fun DescriptionItem(iconPainter: Painter, title: String, description: String) { + Row( + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Icon( + modifier = Modifier.size(TangemTheme.dimens.size24), + painter = iconPainter, + tint = TangemTheme.colors.icon.primary1, + contentDescription = null, + ) + SpacerW24() + Column { + Text( + text = title, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerH4() + Text( + text = description, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + TangemThemePreview { + AskBiometry( + state = AskBiometryUM(), + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewBS() { + TangemThemePreview { + AskBiometry( + state = AskBiometryUM(bottomSheetVariant = true), + ) + } +} \ No newline at end of file diff --git a/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/state/AskBiometryUM.kt b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/state/AskBiometryUM.kt new file mode 100644 index 0000000000..27a511e379 --- /dev/null +++ b/features/ask-biometry/impl/src/main/kotlin/com/tangem/features/askbiometry/impl/ui/state/AskBiometryUM.kt @@ -0,0 +1,11 @@ +package com.tangem.features.askbiometry.impl.ui.state + +import com.tangem.core.ui.extensions.TextReference + +internal data class AskBiometryUM( + val bottomSheetVariant: Boolean = false, + val showProgress: Boolean = false, + val error: TextReference? = null, + val onAllowClick: () -> Unit = {}, + val onDontAllowClick: () -> Unit = {}, +) \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 73be74c713..cbc2e965dd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -228,6 +228,9 @@ include(":features:stories:impl") include(":features:txhistory:api") include(":features:txhistory:impl") +include(":features:ask-biometry:api") +include(":features:ask-biometry:impl") + include(":features:nft:api") include(":features:nft:impl") // endregion Feature modules