diff --git a/app/build.gradle b/app/build.gradle index ff53e7b789..d4e92b6555 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,9 +77,9 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-52' - implementation 'com.tangem.tangem-sdk-kotlin:core:develop-115' - implementation 'com.tangem.tangem-sdk-kotlin:android:develop-115' + implementation 'com.tangem:blockchain:develop-59' + implementation 'com.tangem.tangem-sdk-kotlin:core:develop-127' + implementation 'com.tangem.tangem-sdk-kotlin:android:develop-127' // WebView implementation "androidx.browser:browser:1.3.0" @@ -110,6 +110,9 @@ dependencies { implementation 'com.google.firebase:firebase-config-ktx' implementation 'com.google.firebase:firebase-analytics-ktx' + // AppsFlyer + implementation 'com.appsflyer:af-android-sdk:6.5.1' + // WalletConnect implementation 'com.github.salomonbrys.kotson:kotson:2.5.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f3c400a3ec..f358215715 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ + @@ -35,7 +36,8 @@ android:roundIcon="@mipmap/ic_launcher" android:supportsRtl="true" android:theme="@style/AppTheme" - tools:ignore="GoogleAppIndexingWarning"> + tools:ignore="GoogleAppIndexingWarning" + tools:replace="android:fullBackupContent"> { TransactionDialog.create(state.dialog.dialogData, context) is WalletConnectDialog.PersonalSign -> PersonalSignDialog.create(state.dialog.data, context) + is WalletConnectDialog.BnbTransactionDialog -> + BnbTransactionDialog.create( + data = state.dialog.data, + session = state.dialog.session, + sessionId = state.dialog.sessionId, + cardId = state.dialog.cardId, + dAppName = state.dialog.dAppName, + context = context + ) is BackupDialog.AddMoreBackupCards -> AddMoreBackupCardsDialog.create(context) is BackupDialog.BackupInProgress -> BackupInProgressDialog.create(context) is BackupDialog.UnfinishedBackupFound -> UnfinishedBackupFoundDialog.create(context) diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt index 8ddf2f5ad9..5099d3e1cd 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsEvent.kt @@ -4,12 +4,66 @@ enum class AnalyticsEvent(val event: String) { CARD_IS_SCANNED("card_is_scanned"), TRANSACTION_IS_SENT("transaction_is_sent"), READY_TO_SCAN("ready_to_scan"), + APP_RATING_DISPLAYED("rate_app_warning_displayed"), APP_RATING_DISMISS("dismiss_rate_app_warning"), APP_RATING_NEGATIVE("negative_rate_app_feedback"), APP_RATING_POSITIVE("positive_rate_app_feedback"), + WC_SUCCESS_RESPONSE("wallet_connect_success_response"), WC_INVALID_REQUEST("wallet_connect_invalid_request"), WC_NEW_SESSION("wallet_connect_new_session"), WC_SESSION_DISCONNECTED("wallet_connect_session_disconnected"), + + GET_CARD("get_card"), +} + +enum class AnalyticsParam(val param: String) { + BLOCKCHAIN("blockchain"), + BATCH_ID("batch_id"), + FIRMWARE("firmware"), + ACTION("action"), + ERROR_DESCRIPTION("error_description"), + ERROR_CODE("error_code"), + NEW_SECURITY_OPTION("new_security_option"), + ERROR_KEY("Tangem SDK error key"), + + WALLET_CONNECT_ACTION("wallet_connect_action"), + WALLET_CONNECT_REQUEST("wallet_connect_request"), + WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"), + + SOURCE("source"), +} + +enum class GetCardSourceParams(val param: String) { + WELCOME("welcome"), + ONBOARDING_BUY_MORE("wallet_onboaring_buy_more_cards"), + ONBOARDING("wallet_onboarding") +} + +object Analytics { + enum class ActionToLog(val key: String) { + Scan("tap_scan_task"), + SendTransaction("send_transaction"), + WalletConnectSign("wallet_connect_personal_sign"), + WalletConnectTransaction("wallet_connect_tx_sign"), + ReadPinSettings("read_pin_settings"), + ChangeSecOptions("change_sec_options"), + CreateWallet("create_wallet"), + PurgeWallet("purge_wallet"), + WriteIssuerData("write_issuer_data"), + } + + enum class WcSessionEvent { Disconnect, Connect } + + enum class WcAction { PersonalSign, SignTransaction, SendTransaction } + + sealed class WcAnalyticsEvent { + data class Error(val error: Throwable, val action: WcAction?) : + WcAnalyticsEvent() + + data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent() + data class Action(val action: WcAction) : WcAnalyticsEvent() + data class InvalidRequest(val json: String?) : WcAnalyticsEvent() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt index 1022f85c5a..caa5f0117b 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/AnalyticsHandler.kt @@ -2,14 +2,75 @@ package com.tangem.tap.common.analytics import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError +import com.tangem.tap.common.extensions.filterNotNull -interface AnalyticsHandler { - fun triggerEvent(event: AnalyticsEvent, card: Card? = null, blockchain: String? = null) - fun logCardSdkError( +abstract class AnalyticsHandler { + abstract fun triggerEvent( + event: AnalyticsEvent, + card: Card? = null, + blockchain: String? = null, + params: Map = emptyMap() + ) + + abstract fun logCardSdkError( error: TangemSdkError, - actionToLog: FirebaseAnalyticsHandler.ActionToLog, - parameters: Map? = null, + actionToLog: Analytics.ActionToLog, + parameters: Map? = null, card: Card? = null ) + + abstract fun logError( + error: Throwable, + params: Map = emptyMap() + ) + + protected fun prepareParams(card: Card?, blockchain: String? = null): Map { + return mapOf( + AnalyticsParam.FIRMWARE.param to card?.firmwareVersion?.stringValue, + AnalyticsParam.BATCH_ID.param to card?.batchId, + AnalyticsParam.BLOCKCHAIN.param to blockchain, + ).filterNotNull() + } + + fun logWcEvent(event: Analytics.WcAnalyticsEvent) { + when (event) { + is Analytics.WcAnalyticsEvent.Action -> { + triggerEvent( + event = AnalyticsEvent.WC_SUCCESS_RESPONSE, + params = mapOf( + AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name + ) + ) + } + is Analytics.WcAnalyticsEvent.Error -> { + val params = mapOf( + AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action?.name, + AnalyticsParam.ERROR_DESCRIPTION.param to event.error.message + ) + .filterNotNull() + logError(event.error, params) + } + is Analytics.WcAnalyticsEvent.InvalidRequest -> + triggerEvent( + event = AnalyticsEvent.WC_INVALID_REQUEST, + params = mapOf( + AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json + ).filterNotNull() + ) + is Analytics.WcAnalyticsEvent.Session -> { + val analyticsEvent = when (event.event) { + Analytics.WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED + Analytics.WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION + } + triggerEvent( + event = analyticsEvent, + params = mapOf( + AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url + ).filterNotNull() + ) + } + } + + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/AppsFlyerAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/AppsFlyerAnalyticsHandler.kt new file mode 100644 index 0000000000..9692defe86 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/AppsFlyerAnalyticsHandler.kt @@ -0,0 +1,33 @@ +package com.tangem.tap.common.analytics + +import android.app.Application +import com.appsflyer.AppsFlyerLib +import com.tangem.common.card.Card +import com.tangem.common.core.TangemSdkError + +class AppsFlyerAnalyticsHandler(val context: Application): AnalyticsHandler() { + + override fun triggerEvent( + event: AnalyticsEvent, + card: Card?, + blockchain: String?, + params: Map + ) { + AppsFlyerLib.getInstance().logEvent(context, + event.event, prepareParams(card, blockchain)) + } + + override fun logCardSdkError( + error: TangemSdkError, + actionToLog: Analytics.ActionToLog, + parameters: Map?, + card: Card? + ) { + //TODO + } + + override fun logError(error: Throwable, params: Map) { + //TODO + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt index 33383e1089..106c07aae9 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/FirebaseAnalyticsHandler.kt @@ -7,11 +7,16 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.ktx.Firebase import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError -import com.tangem.tap.common.extensions.filterNotNull -object FirebaseAnalyticsHandler : AnalyticsHandler { - override fun triggerEvent(event: AnalyticsEvent, card: Card?, blockchain: String?) { - Firebase.analytics.logEvent(event.event, setData(card, blockchain)) +object FirebaseAnalyticsHandler : AnalyticsHandler() { + override fun triggerEvent( + event: AnalyticsEvent, + card: Card?, + blockchain: String?, + params: Map + ) { + val fullParams = prepareParams(card, blockchain) + params + Firebase.analytics.logEvent(event.event, fullParams.toBundle()) } fun logException(name: String, throwable: Throwable) { @@ -22,16 +27,20 @@ object FirebaseAnalyticsHandler : AnalyticsHandler { )) } + private fun Map.toBundle(): Bundle { + return bundleOf(*this.toList().toTypedArray()) + } + override fun logCardSdkError( error: TangemSdkError, - actionToLog: ActionToLog, + actionToLog: Analytics.ActionToLog, parameters: Map?, card: Card?, ) { if (error is TangemSdkError.UserCancelled) return val params = parameters?.toMutableMap() ?: mutableMapOf() - if (card != null) params + getParamsFromCard(card) + if (card != null) params + prepareParams(card) params[AnalyticsParam.ACTION] = actionToLog.key params[AnalyticsParam.ERROR_CODE] = error.code.toString() params[AnalyticsParam.ERROR_DESCRIPTION] = error.javaClass.simpleName @@ -44,100 +53,8 @@ object FirebaseAnalyticsHandler : AnalyticsHandler { FirebaseCrashlytics.getInstance().recordException(cardError) } - private fun getParamsFromCard(card: Card): Map { - return mapOf( - AnalyticsParam.FIRMWARE to card.firmwareVersion.stringValue, - AnalyticsParam.BATCH_ID to card.batchId - ).filterNotNull() + override fun logError(error: Throwable, params: Map) { + params.forEach { (key, value) -> FirebaseCrashlytics.getInstance().setCustomKey(key, value) } + FirebaseCrashlytics.getInstance().recordException(error) } - - private fun setData(card: Card?, blockchain: String?): Bundle { - if (card == null) return bundleOf() - return bundleOf( - AnalyticsParam.BLOCKCHAIN.param to (blockchain), - AnalyticsParam.BATCH_ID.param to card.batchId, - AnalyticsParam.FIRMWARE.param to card.firmwareVersion.stringValue - ) - } - - fun logWcEvent(event: WcAnalyticsEvent) { - when (event) { - is WcAnalyticsEvent.Action -> { - - Firebase.analytics.logEvent( - AnalyticsEvent.WC_SUCCESS_RESPONSE.event, bundleOf( - AnalyticsParam.WALLET_CONNECT_ACTION.param to event.action.name - ) - ) - } - is WcAnalyticsEvent.Error -> { - mapOf( - AnalyticsParam.WALLET_CONNECT_ACTION to event.action?.name, - AnalyticsParam.ERROR_DESCRIPTION to event.error.message - ) - .filterNotNull() - .forEach { - FirebaseCrashlytics.getInstance().setCustomKey(it.key.param, it.value) - } - FirebaseCrashlytics.getInstance().recordException(event.error) - } - is WcAnalyticsEvent.InvalidRequest -> - Firebase.analytics.logEvent( - AnalyticsEvent.WC_INVALID_REQUEST.event, bundleOf( - AnalyticsParam.WALLET_CONNECT_REQUEST.param to event.json - ) - ) - is WcAnalyticsEvent.Session -> { - val analyticsEvent = when (event.event) { - WcSessionEvent.Disconnect -> AnalyticsEvent.WC_SESSION_DISCONNECTED - WcSessionEvent.Connect -> AnalyticsEvent.WC_NEW_SESSION - } - Firebase.analytics.logEvent( - analyticsEvent.event, bundleOf( - AnalyticsParam.WALLET_CONNECT_DAPP_URL.param to event.url - ) - ) - } - } - - } - - enum class AnalyticsParam(val param: String) { - BLOCKCHAIN("blockchain"), - BATCH_ID("batch_id"), - FIRMWARE("firmware"), - ACTION("action"), - ERROR_DESCRIPTION("error_description"), - ERROR_CODE("error_code"), - NEW_SECURITY_OPTION("new_security_option"), - ERROR_KEY("Tangem SDK error key"), - WALLET_CONNECT_ACTION("wallet_connect_action"), - WALLET_CONNECT_REQUEST("wallet_connect_request"), - WALLET_CONNECT_DAPP_URL("wallet_connect_dapp_url"), - } - - enum class ActionToLog(val key: String) { - Scan("tap_scan_task"), - SendTransaction("send_transaction"), - WalletConnectSign("wallet_connect_personal_sign"), - WalletConnectTransaction("wallet_connect_tx_sign"), - ReadPinSettings("read_pin_settings"), - ChangeSecOptions("change_sec_options"), - CreateWallet("create_wallet"), - PurgeWallet("purge_wallet"), - WriteIssuerData("write_issuer_data"), - } - - enum class WcSessionEvent { Disconnect, Connect } - - sealed class WcAnalyticsEvent { - data class Error(val error: Throwable, val action: WcAction?) : - WcAnalyticsEvent() - - data class Session(val event: WcSessionEvent, val url: String?) : WcAnalyticsEvent() - data class Action(val action: WcAction) : WcAnalyticsEvent() - data class InvalidRequest(val json: String?) : WcAnalyticsEvent() - } - - enum class WcAction { PersonalSign, SignTransaction, SendTransaction } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt new file mode 100644 index 0000000000..da77c37e05 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/common/analytics/GlobalAnalyticsHandler.kt @@ -0,0 +1,41 @@ +package com.tangem.tap.common.analytics + +import android.app.Application +import com.tangem.common.card.Card +import com.tangem.common.core.TangemSdkError + +class GlobalAnalyticsHandler(val analyticsHandlers: List) : + AnalyticsHandler() { + override fun triggerEvent( + event: AnalyticsEvent, + card: Card?, + blockchain: String?, + params: Map + ) { + analyticsHandlers.forEach { it.triggerEvent(event, card, blockchain) } + } + + override fun logCardSdkError( + error: TangemSdkError, + actionToLog: Analytics.ActionToLog, + parameters: Map?, + card: Card? + ) { + analyticsHandlers.forEach { it.logCardSdkError(error, actionToLog, parameters, card) } + } + + override fun logError(error: Throwable, params: Map) { + analyticsHandlers.forEach { it.logError(error, params) } + } + + companion object { + fun createDefaultAnalyticHandlers(context: Application): GlobalAnalyticsHandler { + return GlobalAnalyticsHandler( + listOf( + FirebaseAnalyticsHandler, + AppsFlyerAnalyticsHandler(context) + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt index 233fa3702d..9a9f5c62b1 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/TangemSdkError.kt @@ -105,6 +105,7 @@ object TangemSdk { is TangemSdkError.WrongInteractionMode -> TangemSdkError.WrongInteractionMode() is TangemSdkError.IssuerSignatureLoadingFailed -> TangemSdkError.IssuerSignatureLoadingFailed() is TangemSdkError.BackupFailedFirmware -> TangemSdkError.BackupFailedFirmware() + is TangemSdkError.UserForgotTheCode -> TangemSdkError.UserForgotTheCode() } } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt b/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt deleted file mode 100644 index 376b56f399..0000000000 --- a/app/src/main/java/com/tangem/tap/common/extensions/ByteArray.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.tap.common.extensions - -/** -[REDACTED_AUTHOR] - */ - -data class ByteArrayKey(val bytes: ByteArray) { - override fun equals(other: Any?): Boolean { - return this === other || other is ByteArrayKey && this.bytes.contentEquals(other.bytes) - } - - override fun hashCode(): Int = bytes.contentHashCode() -} - -fun ByteArray.toMapKey(): ByteArrayKey = ByteArrayKey(this) - -fun ByteArrayKey.bytes(): ByteArray = this.bytes \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt index 60c8a1ec9d..f34b5e9ecf 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Picasso.kt @@ -1,6 +1,7 @@ package com.tangem.tap.common.extensions import android.graphics.PorterDuff +import android.net.Uri import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat @@ -9,6 +10,7 @@ import com.squareup.picasso.Picasso import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.IconsUtil import com.tangem.blockchain.common.Token +import com.tangem.tap.domain.extensions.getCustomIconUrl import com.tangem.wallet.R fun Picasso.loadCurrenciesIcon( @@ -19,7 +21,7 @@ fun Picasso.loadCurrenciesIcon( ) { val url = if (token != null) { - IconsUtil.getTokenIconUri(blockchain, token) + token.getCustomIconUrl()?.let { Uri.parse(it) } ?: IconsUtil.getTokenIconUri(blockchain, token) } else { IconsUtil.getBlockchainIconUri(blockchain) } diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt index 83aa152f76..678caeb9f0 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Store.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Store.kt @@ -18,8 +18,8 @@ fun Store<*>.dispatchOnMain(action: Action) { } } -suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse, addAnalytics: Boolean = true) { - store.state.globalState.tapWalletManager.onCardScanned(scanResponse, addAnalytics) +suspend fun Store<*>.onCardScanned(scanResponse: ScanResponse) { + store.state.globalState.tapWalletManager.onCardScanned(scanResponse) } fun Store<*>.dispatchOpenUrl(url: String) { diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index 70a4fcc286..3d33ceeaa8 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -1,11 +1,11 @@ package com.tangem.tap.common.extensions +import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.common.services.Result import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.amountToCreateAccount -import com.tangem.tap.domain.extensions.isNoAccountError import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.topup.TradeCryptoHelper import com.tangem.tap.features.wallet.redux.AddressData @@ -26,7 +26,7 @@ suspend fun WalletManager.safeUpdate(): Result = try { val blockchain = wallet.blockchain val amountToCreateAccount = blockchain.amountToCreateAccount(wallet.getFirstToken()) - if (blockchain.isNoAccountError(exception) && amountToCreateAccount != null) { + if (exception is BlockchainSdkError.AccountNotFound && amountToCreateAccount != null) { Result.Failure(TapError.WalletManagerUpdate.NoAccountError(amountToCreateAccount.toString())) } else { val message = exception.localizedMessage ?: "An error has occurred. Try later" diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt deleted file mode 100644 index a95488d44f..0000000000 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/Animations.kt +++ /dev/null @@ -1,147 +0,0 @@ -package com.tangem.tap.common.leapfrogWidget - -import android.animation.Animator -import android.animation.AnimatorSet -import android.animation.ObjectAnimator -import android.animation.ValueAnimator -import android.view.View -import android.view.animation.DecelerateInterpolator -import android.view.animation.LinearInterpolator - -/** -[REDACTED_AUTHOR] - */ -//fun View.setForeground(hasForegroundAtEnd: Boolean) { -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { -// foreground = getForegroundDrawable() -// } -//} - -//fun View.getForegroundDrawable(): Drawable? { -// return AppCompatResources.getDrawable(context, R.drawable.shape_rectangle_rounded_8_op_20)?.also { -// it.alpha = 60 -// } -//} - -typealias ProgressListener = (Int) -> Unit - -fun createProgressListener(duration: Long, listener: ProgressListener?): Animator { - val valueAnimator = ValueAnimator.ofInt(0, 100) - valueAnimator.duration = duration - valueAnimator.addUpdateListener { listener?.invoke((it.animatedValue as? Int) ?: 0) } - return valueAnimator -} - -fun View.unfoldAnimation(animDuration: Long, properties: LeapViewProperties): AnimatorSet { - val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) - translate.duration = animDuration - return AnimatorSet().apply { playTogether(translate) } -} - -fun View.foldAnimation(animDuration: Long, properties: LeapViewProperties): AnimatorSet { - val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) - translate.duration = animDuration - - return AnimatorSet().apply { - playSequentially(translate) - } -} - -fun View.leapAnimation(animDuration: Long, properties: LeapViewProperties, overLift: Float): AnimatorSet { - val halfDuration = animDuration / 2 - - val upTo = (height.toFloat() + overLift) * -1 - val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo) - translateUp.duration = halfDuration - translateUp.interpolator = LinearInterpolator() - - val scaleDuration = halfDuration - halfDuration / 5 - val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale) - val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale) - val scale = AnimatorSet().apply { - startDelay = scaleDuration - duration = scaleDuration - interpolator = LinearInterpolator() - playTogether(scaleX, scaleY) - } - - val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd, halfDuration, 100) - val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) - translateDown.startDelay = halfDuration - translateDown.duration = halfDuration - translateDown.interpolator = LinearInterpolator() - - return AnimatorSet().apply { - playTogether(translateUp, scale, elevation, translateDown) - } -} - -fun View.leapBackAnimation(animDuration: Long, properties: LeapViewProperties, calculator: PropertyCalculator): AnimatorSet { - val halfDuration = animDuration / 2 - - val upTo = ((height).toFloat() - calculator.yTranslationFactor) * -1 - val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, upTo) - translateUp.duration = halfDuration - translateUp.interpolator = LinearInterpolator() - - val scaleDuration = halfDuration - halfDuration / 5 - val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale) - val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale) - val scale = AnimatorSet().apply { - startDelay = scaleDuration - duration = scaleDuration - interpolator = LinearInterpolator() - playTogether(scaleX, scaleY) - } - - val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd, halfDuration, 100) - val translateDown = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) - translateDown.startDelay = halfDuration - translateDown.duration = halfDuration - - return AnimatorSet().apply { - playTogether(translateUp, scale, elevation, translateDown) - } -} - -fun View.pullUpAnimation(leapDuration: Long, properties: LeapViewProperties): AnimatorSet { - val pullDuration = leapDuration / 2 - val delayDuration = leapDuration / 2 - - val translateUp = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) - val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale) - val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale) - val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd) - - return AnimatorSet().apply { - startDelay = delayDuration - duration = pullDuration - interpolator = DecelerateInterpolator() - playTogether(translateUp, scaleX, scaleY, elevation) - } -} - -fun View.pullDownAnimation(leapDuration: Long, properties: LeapViewProperties): AnimatorSet { - val pullDuration = leapDuration / 2 - val delayDuration = leapDuration / 4 - - val translate = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, properties.yTranslation) - val scaleX = ObjectAnimator.ofFloat(this, View.SCALE_X, properties.scale) - val scaleY = ObjectAnimator.ofFloat(this, View.SCALE_Y, properties.scale) - val elevation = elevationAnimator(properties.elevationStart, properties.elevationEnd) - - return AnimatorSet().apply { - startDelay = delayDuration - duration = pullDuration - interpolator = DecelerateInterpolator() - playTogether(translate, scaleX, scaleY, elevation) - } -} - -fun View.elevationAnimator(start: Float, end: Float, startDelay: Long? = null, duration: Long? = null): Animator { - val elevationAnimator = ValueAnimator.ofFloat(start, end) - elevationAnimator.addUpdateListener { this.elevation = (it.animatedValue as? Float) ?: start } - startDelay?.let { elevationAnimator.startDelay = it } - duration?.let { elevationAnimator.duration = it } - return elevationAnimator -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt deleted file mode 100644 index be2d63b664..0000000000 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapView.kt +++ /dev/null @@ -1,144 +0,0 @@ -package com.tangem.tap.common.leapfrogWidget - -import android.view.View - -/** -[REDACTED_AUTHOR] - */ -class LeapView( - val view: View, - val index: Int, - position: Int, - private val maximalPosition: Int, - private val calculator: PropertyCalculator, -) { - var state: LeapViewState - private set - - val initialState: LeapViewState - - init { - val initialProperties = createInitialProperty(position) - state = LeapViewState(index, position, position, initialProperties) - initialState = state.copy() - } - - fun initView() { - applyState(initialState) - } - - fun leap(): LeapFrogAnimation { - val previousPosition = state.currentPosition - val currentPosition = when (previousPosition) { - 0 -> maximalPosition - else -> state.currentPosition - 1 - } - - state = state.copy( - currentPosition = currentPosition, - previousPosition = previousPosition, - properties = createLeapAnimationProperty(previousPosition, currentPosition) - ) - - return when { - previousPosition == 0 && currentPosition == maximalPosition -> LeapFrogAnimation.LEAP - else -> LeapFrogAnimation.PULL - } - } - - fun leapBack(): LeapFrogAnimation { - val previousPosition = state.currentPosition - val currentPosition = when (previousPosition) { - maximalPosition -> 0 - else -> state.currentPosition + 1 - } - state = state.copy( - currentPosition = currentPosition, - previousPosition = previousPosition, - properties = createLeapAnimationProperty(previousPosition, currentPosition) - ) - - return when { - previousPosition == maximalPosition && currentPosition == 0 -> LeapFrogAnimation.LEAP - else -> LeapFrogAnimation.PULL - } - } - - fun fold() { - state = state.copy(properties = state.properties.toFold() - ) - } - - fun unfold() { - state = state.copy(properties = state.properties.toUnfold(calculator)) - } - - fun applyState(state: LeapViewState) { - this.state = state - setProperties(this.state.properties) - } - - private fun setProperties(properties: LeapViewProperties) { - view.elevation = properties.elevationEnd - view.translationY = properties.yTranslation - view.scaleX = properties.scale - view.scaleY = properties.scale -// initialProperties.hasForeground - } - - private fun createInitialProperty(endPosition: Int): LeapViewProperties { - return LeapViewProperties( - endPosition, - endPosition, - calculator.scale(endPosition), - calculator.elevation(endPosition, maximalPosition + 1), - calculator.elevation(endPosition, maximalPosition + 1), - calculator.yTranslation(0), - calculator.hasForeground(endPosition), - ) - } - - private fun createLeapAnimationProperty(startPosition: Int, endPosition: Int): LeapViewProperties { - return LeapViewProperties( - startPosition, - endPosition, - calculator.scale(endPosition), - calculator.elevation(startPosition, maximalPosition + 1), - calculator.elevation(endPosition, maximalPosition + 1), - calculator.yTranslation(endPosition), - calculator.hasForeground(endPosition), - ) - } - - private fun LeapViewProperties.toUnfold(calculator: PropertyCalculator): LeapViewProperties { - return this.copy(yTranslation = calculator.yTranslation(this.positionEnd)) - } - - private fun LeapViewProperties.toFold(): LeapViewProperties { - return this.copy(yTranslation = 0f) - } - - override fun toString(): String = "index: $index, position: ${state.currentPosition}, code: ${view.hashCode()}" - -} - -data class LeapViewState( - val index: Int, - val currentPosition: Int, - val previousPosition: Int, - val properties: LeapViewProperties -) - -enum class LeapFrogAnimation { - LEAP, PULL -} - -data class LeapViewProperties( - val positionStart: Int, - val positionEnd: Int, - val scale: Float, - val elevationStart: Float, - val elevationEnd: Float, - val yTranslation: Float, - val hasForeground: Boolean, -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt deleted file mode 100644 index e83f7b8b92..0000000000 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/LeapfrogWidget.kt +++ /dev/null @@ -1,249 +0,0 @@ -package com.tangem.tap.common.leapfrogWidget - -import android.animation.Animator -import android.animation.AnimatorSet -import android.view.animation.AccelerateDecelerateInterpolator -import android.widget.FrameLayout -import androidx.core.animation.doOnEnd -import androidx.core.view.children -import com.tangem.common.extensions.VoidCallback -import com.tangem.tangem_sdk_new.extensions.dpToPx - -/** -[REDACTED_AUTHOR] - */ -class LeapfrogWidget( - private val parentContainer: FrameLayout, - private val calculator: PropertyCalculator = PropertyCalculator(), -) { - private val foldAnimationDuration = 300L - private val leapAnimationDuration = 500L - private val leapBackAnimationDuration = 500L - - private val leapViews = mutableListOf() - - private var foldUnfoldProgress: Int = 100 - private var leapProgress: Int = 100 - private var leapBackProgress: Int = 100 - - init { - calculator.setTranslationConverter { parentContainer.dpToPx(it) } - if (viewIsFullFledged()) { - val maxPosition = parentContainer.childCount - 1 - parentContainer.children.forEachIndexed { index, view -> - val initialPosition = maxPosition - index - leapViews.add(LeapView(view, index, initialPosition, maxPosition, calculator)) - } - } - } - - /** - * Apply initial properties to the views - */ - fun initViews() { - leapViews.forEach { it.initView() } - } - - fun fold(animate: Boolean = true, onEnd: VoidCallback = {}) { - val animator = foldAnimator(animate) - if (animator == null) { - onEnd() - } else { - animator.doOnEnd { onEnd() } - animator.start() - } - } - - fun unfold(animate: Boolean = true, onEnd: VoidCallback = {}) { - val animator = unfoldAnimator(animate) - if (animator == null) { - onEnd() - } else { - animator.doOnEnd { onEnd() } - animator.start() - } - } - - fun foldAnimator(animate: Boolean = true): Animator? { - if (!canFoldUnfold()) return null - - return createFoldUnfoldAnimator(true, animate, foldAnimationDuration) - } - - fun unfoldAnimator(animate: Boolean = true): Animator? { - if (!canFoldUnfold()) return null - - return createFoldUnfoldAnimator(false, animate, foldAnimationDuration) - } - - private fun createFoldUnfoldAnimator(isFoldAnimation: Boolean, animate: Boolean, duration: Long): Animator { - val animatorsList = mutableListOf() - val animateDuration = if (animate) duration else 0 - leapViews.forEach { - if (isFoldAnimation) { - it.fold() - animatorsList.add(it.view.foldAnimation(animateDuration, it.state.properties)) - } else { - it.unfold() - animatorsList.add(it.view.unfoldAnimation(animateDuration, it.state.properties)) - } - } - animatorsList.add(createProgressListener(animateDuration) { foldUnfoldProgress = it }) - - val animator = AnimatorSet() - animator.interpolator = AccelerateDecelerateInterpolator() - animator.playTogether(animatorsList.toList()) - return animator - } - - fun leap(animate: Boolean = true, onEnd: VoidCallback = {}) { - if (!canLeap()) { - onEnd() - return - } - - val duration = if (animate) leapAnimationDuration else 0 - val animatorsList = mutableListOf() - leapViews.forEach { - when (it.leap()) { - LeapFrogAnimation.LEAP -> { - val overLift = calculator.overLift(leapViews.size) - animatorsList.add(it.view.leapAnimation(duration, it.state.properties, overLift)) - } - LeapFrogAnimation.PULL -> { - animatorsList.add(it.view.pullUpAnimation(duration, it.state.properties)) - } - } - } - animatorsList.add(createProgressListener(duration) { leapProgress = it }) - - val animator = AnimatorSet() - animator.playTogether(animatorsList.toList()) - animator.doOnEnd { onEnd() } - animator.start() - } - - fun leapBack(animate: Boolean = true, onEnd: VoidCallback = {}) { - if (!canLeapBack()) { - onEnd() - return - } - - val duration = if (animate) leapBackAnimationDuration else 0 - val animatorsList = mutableListOf() - leapViews.forEach { - when (it.leapBack()) { - LeapFrogAnimation.LEAP -> { - animatorsList.add(it.view.leapBackAnimation(duration, it.state.properties, calculator)) - } - LeapFrogAnimation.PULL -> { - animatorsList.add(it.view.pullDownAnimation(duration, it.state.properties)) - } - } - } - animatorsList.add(createProgressListener(duration) { leapBackProgress = it }) - - val animator = AnimatorSet() - animator.playTogether(animatorsList.toList()) - animator.doOnEnd { onEnd() } - animator.start() - } - - fun getViewsCount(): Int = parentContainer.childCount - - fun getViewPositionByIndex(index: Int): Int = leapViews.first { it.index == index }.state.currentPosition - - fun getViewByPosition(position: Int): LeapView = leapViews.first { it.state.currentPosition == position } - - fun getState(): LeapfrogWidgetState = LeapfrogWidgetState(leapViews.map { it.state }) - - fun applyState(state: LeapfrogWidgetState) { - state.leapViewStates.forEach { viewState -> - leapViews.firstOrNull { it.index == viewState.index }?.applyState(viewState) - } - } - - private fun viewIsFullFledged(): Boolean = parentContainer.childCount > 1 - - private fun canFoldUnfold(): Boolean { - return when { - !viewIsFullFledged() -> false - foldUnfoldInProgress() -> false - leapInProgress() || leapBackInProgress() -> false - else -> true - } - } - - private fun canLeap(): Boolean { - return when { - !viewIsFullFledged() -> false - leapBackInProgress() -> false - leapProgress < 70 -> false - else -> true - } - } - - private fun canLeapBack(): Boolean { - return when { - !viewIsFullFledged() -> false - leapInProgress() -> false - leapBackProgress < 70 -> false - else -> true - } - } - - private fun foldUnfoldInProgress(): Boolean = foldUnfoldProgress != 100 - private fun leapInProgress(): Boolean = leapProgress != 100 - private fun leapBackInProgress(): Boolean = leapBackProgress != 100 -} - -data class LeapfrogWidgetState( - val leapViewStates: List -) - -class PropertyCalculator( - val elevationFactor: Float = 1f, - val decreaseScaleFactor: Float = 0.15f, - val yTranslationFactor: Float = 35f, - val decreaseOverLiftingFactor: Float = 0.85f, -) { - // 0 view it is view on the top of stack, but it is last in parent.children - - private var translationValueConverter: ((Float) -> Float)? = null - fun setTranslationConverter(converter: (Float) -> Float) { - translationValueConverter = converter - } - - fun scale(position: Int): Float { - return 1f - decreaseScaleFactor * position - } - - fun elevation(position: Int, count: Int): Float { - return elevationFactor * (count - 1 - position) - } - - fun yTranslation(position: Int): Float { - val yTranslation = yTranslationFactor * position - return convertTranslation(yTranslation) - } - - fun hasForeground(position: Int): Boolean { - return position != 0 - } - - fun overLift(viewCount: Int): Float { - val initialOverLift = when { - yTranslationFactor < 0f -> yTranslationFactor * decreaseOverLiftingFactor * viewCount * -1 - else -> 10f - } - val scaleOverLift = when { - decreaseScaleFactor == 0f -> initialOverLift - else -> initialOverLift * decreaseOverLiftingFactor - initialOverLift * decreaseScaleFactor - } - return convertTranslation(scaleOverLift) - } - - private fun convertTranslation(value: Float): Float { - return translationValueConverter?.invoke(value) ?: value - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt index 1a41ff35be..36e92a0ebe 100644 --- a/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt +++ b/app/src/main/java/com/tangem/tap/common/leapfrogWidget/TestLeapfrogFragment.kt @@ -5,6 +5,8 @@ import android.view.View import android.widget.FrameLayout import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidgetState import com.tangem.tap.domain.twins.TwinsCardWidget import com.tangem.wallet.R import kotlinx.android.synthetic.main.test_leapfrog_fragment.* diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index bbea6e43f9..5f0db04ae5 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.global import com.tangem.blockchain.common.WalletManager import com.tangem.common.CompletionResult import com.tangem.common.core.TangemError +import com.tangem.tap.common.analytics.GlobalAnalyticsHandler import com.tangem.tap.common.redux.* import com.tangem.tap.domain.TapError import com.tangem.tap.domain.configurable.config.ConfigManager @@ -69,6 +70,7 @@ sealed class GlobalAction : Action { data class SetConfigManager(val configManager: ConfigManager) : GlobalAction() data class SetWarningManager(val warningManager: WarningMessagesManager) : GlobalAction() data class SetFeedbackManager(val feedbackManager: FeedbackManager) : GlobalAction() + data class SetAnanlyticHandlers(val analyticsHandlers: GlobalAnalyticsHandler) : GlobalAction() data class SendFeedback(val emailData: EmailData) : GlobalAction() data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction() diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index b16fbe21c7..6200feb947 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -4,7 +4,6 @@ import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result import com.tangem.tap.* -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.withMainContext @@ -89,9 +88,8 @@ private val globalMiddlewareHandler: Middleware = { dispatch, appState is GlobalAction.ScanCard -> { scope.launch { val result = tangemSdkManager.scanProduct( - FirebaseAnalyticsHandler, + store.state.globalState.analyticsHandlers, currenciesRepository, - action.shouldDeriveWC, action.messageResId ) withMainContext { diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index e549a2f386..94621117bc 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -46,6 +46,8 @@ fun globalReducer(action: Action, state: AppState): GlobalState { globalState.copy(configManager = action.configManager) } is GlobalAction.SetWarningManager -> globalState.copy(warningManager = action.warningManager) + is GlobalAction.SetAnanlyticHandlers -> + globalState.copy(analyticsHandlers = action.analyticsHandlers) is GlobalAction.UpdateWalletSignedHashes -> { val card = globalState.scanResponse?.card ?: return globalState val wallet = card.wallet(action.walletPublicKey) ?: return globalState diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index f634a6a0ff..c607125cb8 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -1,5 +1,6 @@ package com.tangem.tap.common.redux.global +import com.tangem.tap.common.analytics.AnalyticsHandler import com.tangem.tap.common.entities.TapCurrency.Companion.DEFAULT_FIAT_CURRENCY import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.domain.PayIdManager @@ -28,6 +29,7 @@ data class GlobalState( val dialog: StateDialog? = null, val moonpayStatus: MoonpayStatus? = null, val resources: AndroidResources = AndroidResources(), + val analyticsHandlers: AnalyticsHandler? = null, ) : StateType diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt index 4c3f0c78d4..2605d263c3 100644 --- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt @@ -14,18 +14,19 @@ import com.tangem.common.core.CardIdDisplayFormat import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.Config import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.hdWallet.DerivationPath import com.tangem.operations.CommandResponse +import com.tangem.operations.derivation.DerivationTaskResponse +import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask import com.tangem.operations.pins.CheckUserCodesCommand import com.tangem.operations.pins.CheckUserCodesResponse import com.tangem.operations.pins.SetUserCodeCommand +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsEvent import com.tangem.tap.common.analytics.AnalyticsHandler import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler -import com.tangem.tap.common.extensions.ByteArrayKey import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask -import com.tangem.tap.domain.tasks.DerivationTask -import com.tangem.tap.domain.tasks.DerivationTaskResponse import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask import com.tangem.tap.domain.tasks.product.ScanProductTask import com.tangem.tap.domain.tasks.product.ScanResponse @@ -39,16 +40,15 @@ import kotlin.coroutines.suspendCoroutine class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) { suspend fun scanProduct( - analyticsHandler: AnalyticsHandler, + analyticsHandler: AnalyticsHandler?, currenciesRepository: CurrenciesRepository, - shouldDeriveWC: Boolean, messageRes: Int? = null, ): CompletionResult { - analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) + analyticsHandler?.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null) val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header)) return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository), null, message) - .also { sendScanFailuresToAnalytics(analyticsHandler, it) } + .also { sendScanResultsToAnalytics(analyticsHandler, it) } } suspend fun createProductWallet( @@ -61,14 +61,24 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co ) } - private fun sendScanFailuresToAnalytics( - analyticsHandler: AnalyticsHandler, + private fun sendScanResultsToAnalytics( + analyticsHandler: AnalyticsHandler?, result: CompletionResult ) { - if (result is CompletionResult.Failure && result.error is TangemSdkError) { - (result.error as? TangemSdkError)?.let { error -> - analyticsHandler.logCardSdkError(error, FirebaseAnalyticsHandler.ActionToLog.Scan) - } + when (result) { + is CompletionResult.Success -> + analyticsHandler?.triggerEvent( + event = AnalyticsEvent.CARD_IS_SCANNED, + card = result.data.card, + blockchain = result.data.walletData?.blockchain + ) + is CompletionResult.Failure -> + (result.error as? TangemSdkError)?.let { error -> + analyticsHandler?.logCardSdkError( + error = error, + actionToLog = Analytics.ActionToLog.Scan + ) + } } } @@ -81,7 +91,7 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co cardId: String, derivations: Map> ): CompletionResult { - return runTaskAsyncReturnOnMain(DerivationTask(derivations), cardId) + return runTaskAsyncReturnOnMain(DeriveMultipleWalletPublicKeysTask(derivations), cardId) } suspend fun resetToFactorySettings(card: Card): CompletionResult { diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt index 9f6d0f13e1..cfd628a093 100644 --- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt @@ -2,8 +2,6 @@ package com.tangem.tap.domain import com.tangem.blockchain.common.* import com.tangem.common.services.Result -import com.tangem.tap.common.analytics.AnalyticsEvent -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.FiatCurrencyName @@ -71,10 +69,7 @@ class TapWalletManager { handleFiatRatesResult(results) } - suspend fun onCardScanned(data: ScanResponse, addAnalyticsEvent: Boolean = false) { - if (addAnalyticsEvent) { - FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.CARD_IS_SCANNED, data.card, data.walletData?.blockchain) - } + suspend fun onCardScanned(data: ScanResponse) { store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(data.card) updateConfigManager(data) diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index cae1861582..412537befb 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -11,6 +11,7 @@ data class Config( val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de", val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE", val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C", + val appsFlyerDevKey: String = "", val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(), val isSendingToPayIdEnabled: Boolean = true, val isTopUpEnabled: Boolean = false, @@ -84,7 +85,8 @@ class ConfigManager( blockchairAuthorizationToken = values.blockchairAuthorizationToken, blockcypherTokens = values.blockcypherTokens, infuraProjectId = values.infuraProjectId - ) + ), + appsFlyerDevKey = values.appsFlyerDevKey, ) defaultConfig = defaultConfig.copy( coinMarketCapKey = values.coinMarketCapKey, @@ -95,7 +97,8 @@ class ConfigManager( blockchairAuthorizationToken = values.blockchairAuthorizationToken, blockcypherTokens = values.blockcypherTokens, infuraProjectId = values.infuraProjectId - ) + ), + appsFlyerDevKey = values.appsFlyerDevKey, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index c71a4a16dd..edbbf6345c 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -19,6 +19,7 @@ class ConfigValueModel( val blockchairAuthorizationToken: String?, val blockcypherTokens: Set?, val infuraProjectId: String?, + val appsFlyerDevKey: String, ) class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) { diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt index 0563eb8482..673ef1787b 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Blockchain.kt @@ -6,14 +6,6 @@ import com.tangem.common.card.EllipticCurve import java.math.BigDecimal -fun Blockchain.isNoAccountError(exception: Throwable): Boolean { - return when (this) { - Blockchain.Stellar, Blockchain.XRP -> - exception.message?.contains("Account not found") == true - else -> false - } -} - fun Blockchain.amountToCreateAccount(token: Token? = null): BigDecimal? { return when (this) { Blockchain.Stellar -> if (token?.symbol == NODL) BigDecimal(1.5) else BigDecimal.ONE diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/Token.kt b/app/src/main/java/com/tangem/tap/domain/extensions/Token.kt new file mode 100644 index 0000000000..6963ed3682 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/extensions/Token.kt @@ -0,0 +1,17 @@ +package com.tangem.tap.domain.extensions + +import com.tangem.blockchain.common.Token + +/** +[REDACTED_AUTHOR] + */ + +private val tokenCustomIconUrls = mutableMapOf() + +fun Token.getCustomIconUrl(): String? { + return tokenCustomIconUrls[this.contractAddress] +} + +fun Token.setCustomIconUrl(url: String) { + tokenCustomIconUrls[this.contractAddress] = url +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt index b505619739..3b58ec418e 100644 --- a/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt +++ b/app/src/main/java/com/tangem/tap/domain/extensions/WalletManagerFactory.kt @@ -6,7 +6,7 @@ import com.tangem.blockchain.common.WalletManagerFactory import com.tangem.common.card.CardWallet import com.tangem.common.card.EllipticCurve import com.tangem.common.extensions.hexToBytes -import com.tangem.tap.common.extensions.toMapKey +import com.tangem.common.extensions.toMapKey import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.tasks.product.ScanResponse @@ -33,13 +33,23 @@ fun WalletManagerFactory.makeWalletManagerForApp( } seedKey != null -> { val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()] - val derivedKey = derivedKeys?.firstOrNull { it.derivationPath == blockchain.derivationPath() } + val derivedKey = derivedKeys?.get(blockchain.derivationPath()) ?: return null - makeWalletManager(card.cardId, environmentBlockchain, seedKey, derivedKey) + makeWalletManager( + cardId = card.cardId, + blockchain = environmentBlockchain, + seedKey = wallet.publicKey, + derivedKey = derivedKey + ) } else -> { - makeWalletManager(card.cardId, environmentBlockchain, wallet.publicKey, wallet.curve) + makeWalletManager( + cardId = card.cardId, + blockchain = environmentBlockchain, + walletPublicKey = wallet.publicKey, + curve = wallet.curve + ) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt index 8f03e068b5..3c471f4bc4 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/DerivationTask.kt @@ -1,50 +1,55 @@ -package com.tangem.tap.domain.tasks - -import com.tangem.common.CompletionResult -import com.tangem.common.core.CardSession -import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.CompletionCallback -import com.tangem.common.hdWallet.DerivationPath -import com.tangem.operations.CommandResponse -import com.tangem.operations.derivation.DeriveWalletPublicKeysTask -import com.tangem.operations.derivation.ExtendedPublicKeyList -import com.tangem.tap.common.extensions.ByteArrayKey - -class DerivationTaskResponse( - val entries: Map -): CommandResponse - -class DerivationTask( - private val derivations: Map> -) : CardSessionRunnable { - - val response: MutableMap = mutableMapOf() - - override fun run(session: CardSession, callback: CompletionCallback) { - derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback) - } - - private fun derive( - keys: List, - index: Int, - session: CardSession, - callback: CompletionCallback - ) { - if (index == keys.count()) { - callback(CompletionResult.Success(DerivationTaskResponse(response.toMap()))) - return - } - - val key = keys[index] - val paths = derivations[key]!! - DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result -> - when (result) { - is CompletionResult.Success -> { - response[key] = result.data - derive(keys = keys, index = index + 1, session = session, callback = callback) - } - is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) - } - } - } -} \ No newline at end of file +//package com.tangem.tap.domain.tasks +// +//import com.tangem.common.CompletionResult +//import com.tangem.common.core.CardSession +//import com.tangem.common.core.CardSessionRunnable +//import com.tangem.common.core.CompletionCallback +//import com.tangem.common.hdWallet.DerivationPath +//import com.tangem.common.hdWallet.ExtendedPublicKey +//import com.tangem.operations.CommandResponse +//import com.tangem.operations.derivation.DeriveWalletPublicKeysTask +//import com.tangem.tap.common.extensions.ByteArrayKey +// +//class ExtendedPublicKeyList( +// items: Collection +//): ArrayList(items), CommandResponse +// +// +//class DerivationTaskResponse( +// val entries: Map +//): CommandResponse +// +//class DerivationTask( +// private val derivations: Map> +//) : CardSessionRunnable { +// +// val response: MutableMap = mutableMapOf() +// +// override fun run(session: CardSession, callback: CompletionCallback) { +// derive(keys = derivations.keys.toList(), index = 0, session = session, callback = callback) +// } +// +// private fun derive( +// keys: List, +// index: Int, +// session: CardSession, +// callback: CompletionCallback +// ) { +// if (index == keys.count()) { +// callback(CompletionResult.Success(DerivationTaskResponse(response.toMap()))) +// return +// } +// +// val key = keys[index] +// val paths = derivations[key]!! +// DeriveWalletPublicKeysTask(key.bytes, paths).run(session) { result -> +// when (result) { +// is CompletionResult.Success -> { +// response[key] = result.data[key] +// derive(keys = keys, index = index + 1, session = session, callback = callback) +// } +// is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) +// } +// } +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt index 89bdb12dd2..e75da7ca72 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt @@ -6,16 +6,16 @@ import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard -import com.tangem.common.hdWallet.ExtendedPublicKey +import com.tangem.common.extensions.toMapKey import com.tangem.operations.CommandResponse import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.backup.StartPrimaryCardLinkingTask +import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask +import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.operations.wallet.CreateWalletResponse import com.tangem.operations.wallet.CreateWalletTask -import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.domain.ProductType import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain -import com.tangem.tap.domain.tasks.DerivationTask import com.tangem.tap.domain.tasks.product.CreateWalletsTask import com.tangem.tap.domain.tasks.product.KeyWalletPublicKey import com.tangem.tap.domain.tasks.product.ProductCommandProcessor @@ -24,7 +24,7 @@ import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets data class CreateProductWalletTaskResponse( val card: Card, - val derivedKeys: Map> = mapOf(), + val derivedKeys: Map = mapOf(), val primaryCard: PrimaryCard? = null ) : CommandResponse @@ -170,7 +170,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor when (result) { is CompletionResult.Success -> { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt index 506f5aa9c3..05ab2ed34b 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/ScanProductTask.kt @@ -12,19 +12,20 @@ import com.tangem.common.core.CardSession import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemError import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.ByteArrayKey import com.tangem.common.extensions.guard import com.tangem.common.extensions.toHexString +import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath -import com.tangem.common.hdWallet.ExtendedPublicKey import com.tangem.operations.CommandResponse import com.tangem.operations.PreflightReadMode import com.tangem.operations.PreflightReadTask import com.tangem.operations.ScanTask import com.tangem.operations.backup.PrimaryCard import com.tangem.operations.backup.StartPrimaryCardLinkingTask +import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask +import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand -import com.tangem.tap.common.extensions.ByteArrayKey -import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.domain.ProductType import com.tangem.tap.domain.TapSdkError import com.tangem.tap.domain.TapWorkarounds @@ -33,7 +34,6 @@ import com.tangem.tap.domain.TapWorkarounds.isExcluded import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease import com.tangem.tap.domain.extensions.getPrimaryCurve import com.tangem.tap.domain.extensions.getSingleWallet -import com.tangem.tap.domain.tasks.DerivationTask import com.tangem.tap.domain.tokens.CurrenciesRepository import com.tangem.tap.domain.twins.TwinsHelper import com.tangem.tap.preferencesStorage @@ -43,7 +43,7 @@ data class ScanResponse( val productType: ProductType, val walletData: WalletData?, val secondTwinPublicKey: String? = null, - val derivedKeys: Map> = mapOf(), + val derivedKeys: Map = mapOf(), val primaryCard: PrimaryCard? = null ) : CommandResponse { @@ -253,7 +253,7 @@ private class ScanWalletProcessor( return } - DerivationTask(derivations).run(session) { result -> + DeriveMultipleWalletPublicKeysTask(derivations).run(session) { result -> when (result) { is CompletionResult.Success -> { val response = ScanResponse( diff --git a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt index fc1ae2bcb2..02d42e5869 100644 --- a/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/tokens/CurrenciesRepository.kt @@ -9,7 +9,10 @@ import com.squareup.moshi.Types import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.common.card.FirmwareVersion +import com.tangem.common.json.MoshiJsonConverter import com.tangem.tap.common.extensions.readJsonFileToString +import com.tangem.tap.domain.extensions.getCustomIconUrl +import com.tangem.tap.domain.extensions.setCustomIconUrl import com.tangem.tap.network.createMoshi class CurrenciesRepository(val context: Application) { @@ -113,24 +116,28 @@ class CurrenciesRepository(val context: Application) { if (isTestNet) BSC_TESTNET_TOKENS_FILE_NAME else BSC_TOKENS_FILE_NAME val binanceTokensFileName = if (isTestNet) BINANCE_TESTNET_TOKENS_FILE_NAME else BINANCE_TOKENS_FILE_NAME + val avalancheTokensFileName = + if (isTestNet) AVALANCHE_TESTNET_TOKENS_FILE_NAME else AVALANCHE_TOKENS_FILE_NAME val ethereumTokensJson = context.assets.readJsonFileToString(ethereumTokensFileName) val bscTokensJson = context.assets.readJsonFileToString(bscTokensFileName) val binanceTokensJson = context.assets.readJsonFileToString(binanceTokensFileName) + val avalancheTokensJson = context.assets.readJsonFileToString(avalancheTokensFileName) return tokensAdapter.fromJson(ethereumTokensJson)!!.map { it.toToken() } + - tokensAdapter.fromJson(bscTokensJson)!!.map { it.toToken() } + - tokensAdapter.fromJson(binanceTokensJson)!!.mapNotNull { - // temporary exclude Binance BEP-8 tokens - if (it.type != null && it.type == BINANCE_TOKEN_TYPE_BEP8) null else it.toToken() - } + tokensAdapter.fromJson(bscTokensJson)!!.map { it.toToken() } + + tokensAdapter.fromJson(binanceTokensJson)!!.mapNotNull { + // temporary exclude Binance BEP-8 tokens + if (it.type != null && it.type == BINANCE_TOKEN_TYPE_BEP8) null else it.toToken() + } + + tokensAdapter.fromJson(avalancheTokensJson)!!.map { it.toToken() } } fun getBlockchains( - cardFirmware: FirmwareVersion?, + cardFirmware: FirmwareVersion, isTestNet: Boolean = false ): List { - return if (cardFirmware == null || cardFirmware < FirmwareVersion.MultiWalletAvailable) { + return if (cardFirmware < FirmwareVersion.MultiWalletAvailable) { Blockchain.secp256k1Blockchains(isTestNet) } else { Blockchain.secp256k1Blockchains(isTestNet) + Blockchain.ed25519OnlyBlockchains(isTestNet) @@ -144,6 +151,9 @@ class CurrenciesRepository(val context: Application) { private const val BSC_TESTNET_TOKENS_FILE_NAME = "bsc_tokens_testnet" private const val BINANCE_TOKENS_FILE_NAME = "binance_tokens" private const val BINANCE_TESTNET_TOKENS_FILE_NAME = "binance_tokens_testnet" + private const val AVALANCHE_TOKENS_FILE_NAME = "avalanche_tokens" + private const val AVALANCHE_TESTNET_TOKENS_FILE_NAME = "avalanche_tokens_testnet" + private const val FILE_NAME_PREFIX_TOKENS = "tokens" private const val FILE_NAME_PREFIX_BLOCKCHAINS = "blockchains" @@ -152,6 +162,18 @@ class CurrenciesRepository(val context: Application) { fun getFileNameForTokens(cardId: String): String = "${FILE_NAME_PREFIX_TOKENS}_$cardId" fun getFileNameForBlockchains(cardId: String): String = "${FILE_NAME_PREFIX_BLOCKCHAINS}_$cardId" + + fun injectDaoBlockchain(context: Context, tokenFileName: String, blockchain: Blockchain): String? { + val tokenJson = context.assets.readJsonFileToString(tokenFileName) + val converter = MoshiJsonConverter.default() + val rawTokensList = converter.fromJson>>(tokenJson) ?: return null + + rawTokensList.forEach { + it["blockchain"] = BlockchainDao.fromBlockchain(blockchain) + } + + return converter.toJson(rawTokensList) + } } } @@ -163,6 +185,7 @@ data class TokenDao( val decimalCount: Int, @Json(name = "blockchain") val blockchainDao: BlockchainDao, + val customIconUrl: String? = null, val type: String? = null ) { fun toToken(): Token { @@ -172,7 +195,9 @@ data class TokenDao( contractAddress = contractAddress, decimals = decimalCount, blockchain = blockchainDao.toBlockchain() - ) + ).apply { + customIconUrl?.let { this.setCustomIconUrl(it) } + } } companion object { @@ -182,7 +207,8 @@ data class TokenDao( symbol = token.symbol, contractAddress = token.contractAddress, decimalCount = token.decimals, - blockchainDao = BlockchainDao.fromBlockchain(token.blockchain) + blockchainDao = BlockchainDao.fromBlockchain(token.blockchain), + customIconUrl = token.getCustomIconUrl() ) } } @@ -205,8 +231,7 @@ data class BlockchainDao( companion object { fun fromBlockchain(blockchain: Blockchain): BlockchainDao { val name = blockchain.name.removeSuffix("Testnet").lowercase() - val isTestnet = blockchain.name.endsWith("Testnet") - return BlockchainDao(name, isTestnet) + return BlockchainDao(name, blockchain.isTestnet()) } } } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt index 861f3db801..53bd278cc7 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt @@ -11,12 +11,17 @@ import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.toHexString import com.tangem.operations.wallet.CreateWalletResponse -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.AnalyticsHandler import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.network.createMoshi import com.tangem.tap.tangemSdkManager -class TwinCardsManager(private val card: Card, assetReader: AssetReader) { +class TwinCardsManager( + private val card: Card, + assetReader: AssetReader, + val analyticsHandler: AnalyticsHandler? +) { private val currentCardId: String = card.cardId @@ -31,9 +36,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) { is CompletionResult.Success -> currentCardPublicKey = response.data.wallet.publicKey.toHexString() is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + analyticsHandler?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.CreateWallet, + Analytics.ActionToLog.CreateWallet, card = card ) } @@ -61,9 +66,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) { } is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + analyticsHandler?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.CreateWallet, + Analytics.ActionToLog.CreateWallet, card = card ) } @@ -81,9 +86,9 @@ class TwinCardsManager(private val card: Card, assetReader: AssetReader) { is CompletionResult.Success -> Result.Success(response.data) is CompletionResult.Failure -> { (response.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + analyticsHandler?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.WriteIssuerData, + Analytics.ActionToLog.WriteIssuerData, card = card ) } diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt index c8f3c02952..becb529e80 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsWidget.kt @@ -6,9 +6,10 @@ import android.animation.PropertyValuesHolder import android.view.View import androidx.core.animation.doOnEnd import com.tangem.common.extensions.VoidCallback -import com.tangem.tap.common.leapfrogWidget.LeapView -import com.tangem.tap.common.leapfrogWidget.LeapViewState -import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget + /** [REDACTED_AUTHOR] diff --git a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt index 94ce765f12..37f9715e19 100644 --- a/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/twins/WriteProtectedIssuerDataTask.kt @@ -8,7 +8,7 @@ import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.calculateSha256 import com.tangem.common.extensions.guard -import com.tangem.common.files.FileHashHelper +import com.tangem.operations.files.FileHashHelper import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand import com.tangem.operations.issuerAndUserData.ReadIssuerDataResponse import com.tangem.operations.issuerAndUserData.WriteIssuerDataCommand diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt new file mode 100644 index 0000000000..a8f3eb83c1 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/BnbHelper.kt @@ -0,0 +1,79 @@ +package com.tangem.tap.domain.walletconnect + +import com.github.salomonbrys.kotson.registerTypeAdapter +import com.google.gson.GsonBuilder +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.extensions.calculateSha256 +import com.tangem.tap.features.details.redux.walletconnect.BinanceMessageData +import com.tangem.tap.features.details.redux.walletconnect.TradeData +import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder +import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder +import com.trustwallet.walletconnect.models.binance.tradeOrderSerializer +import timber.log.Timber + +class BnbHelper { + companion object { + + fun createMessageData(order: WCBinanceTransferOrder): BinanceMessageData.Transfer { + val input = order.msgs.first().inputs.first() + val output = order.msgs.first().inputs.first() + + val currency = + input.coins.firstOrNull()?.denom ?: Blockchain.Binance + val amount = input.coins + .mapNotNull { if (it.denom == currency) it.amount else null } + .sum() + .toBigDecimal() + .movePointRight(Blockchain.Binance.decimals()) + .toString() + + val gson = GsonBuilder() + .registerTypeAdapter(tradeOrderSerializer) + .serializeNulls() + .create() + + return BinanceMessageData.Transfer( + outputAddress = output.address, + amount = amount, + address = input.address, + data = gson.toJson(order).toByteArray().calculateSha256() + ) + } + + fun createMessageData(order: WCBinanceTradeOrder): BinanceMessageData.Trade { + val address = order.msgs.first().sender + + val tradeData = order.msgs.map { + val price = it.price.toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripTrailingZeros() + val quantity = it.quantity.toBigDecimal() + .movePointLeft(Blockchain.Binance.decimals()) + .stripTrailingZeros() + + val amount = price * quantity + val symbol = it.symbol.substringBefore("-") + + TradeData( + price = "$price ${Blockchain.Binance.currency}", + quantity = "$quantity $symbol", + amount = "$amount ${Blockchain.Binance.currency}", + symbol = symbol + ) + } + val gson = GsonBuilder() + .registerTypeAdapter(tradeOrderSerializer) + .serializeNulls() + .create() + val serialized = gson.toJson(order) + Timber.d(serialized) + + return BinanceMessageData.Trade( + tradeData = tradeData, + address = address, + data = serialized.toByteArray().calculateSha256() + ) + } + + } +} diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt new file mode 100644 index 0000000000..fe8a593787 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/EthSignHelper.kt @@ -0,0 +1,99 @@ +package com.tangem.tap.domain.walletconnect + +import com.github.salomonbrys.kotson.fromJson +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.JsonParser +import com.google.gson.annotations.SerializedName +import com.trustwallet.walletconnect.JSONRPC_VERSION +import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage + +class EthSignHelper { + companion object { + private val gson: Gson by lazy { + GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .create() + } + + fun tryToParseEthTypedMessage(data: String): WCEthereumSignMessage? { + val request = + gson.fromJson(data) + return if (request.method == WCMethodExtended.ETH_SIGN_TYPE_DATA_V4) { + WCEthereumSignMessage( + listOf( + request.params[0].asString, + request.params[1].asString, + ), + WCEthereumSignMessage.WCSignType.TYPED_MESSAGE + ) + } else { + null + } + } + + fun tryToParseEthTypedMessageString(message: String): String? { + return try { + val messageString = message + .replace("\\", "") + .removePrefix("\"") + .removeSuffix("\"") + val messageJson = JsonParser().parse(messageString) + val filteredMap = gson.fromJson>(messageJson) + .filterKeys { it == "domain" || it == "message"} + + gson.toJson(filteredMap) + } catch (exception: Exception) { + null + } + + } + } +} + +data class CustomJsonRpcRequests( + val id: Long, + val jsonrpc: String = JSONRPC_VERSION, + val method: WCMethodExtended?, + val params: JsonArray +) + +enum class WCMethodExtended { + @SerializedName("wc_sessionRequest") + SESSION_REQUEST, + + @SerializedName("wc_sessionUpdate") + SESSION_UPDATE, + + @SerializedName("eth_sign") + ETH_SIGN, + + @SerializedName("personal_sign") + ETH_PERSONAL_SIGN, + + @SerializedName("eth_signTypedData") + ETH_SIGN_TYPE_DATA, + + @SerializedName("eth_signTypedData_v4") + ETH_SIGN_TYPE_DATA_V4, + + @SerializedName("eth_signTransaction") + ETH_SIGN_TRANSACTION, + + @SerializedName("eth_sendTransaction") + ETH_SEND_TRANSACTION, + + @SerializedName("bnb_sign") + BNB_SIGN, + + @SerializedName("bnb_tx_confirmation") + BNB_TRANSACTION_CONFIRM, + + @SerializedName("get_accounts") + GET_ACCOUNTS, + + @SerializedName("trust_signTransaction") + SIGN_TRANSACTION; +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt index 7c74264429..49510ff791 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectManager.kt @@ -2,7 +2,7 @@ package com.tangem.tap.domain.walletconnect import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.guard -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.features.details.redux.walletconnect.* @@ -11,6 +11,7 @@ import com.tangem.tap.store import com.tangem.tap.walletConnectRepository import com.trustwallet.walletconnect.WCClient import com.trustwallet.walletconnect.models.WCPeerMeta +import com.trustwallet.walletconnect.models.binance.* import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction import com.trustwallet.walletconnect.models.session.WCSession @@ -22,6 +23,7 @@ import okhttp3.logging.HttpLoggingInterceptor import timber.log.Timber import java.util.* import java.util.concurrent.TimeUnit +import kotlin.collections.set class WalletConnectManager { @@ -60,6 +62,15 @@ class WalletConnectManager { setupConnectionTimeoutCheck(session) } + fun updateSession(session: WalletConnectSession) { + val updatedSession = sessions[session.session]?.copy( + wallet = session.wallet + ) + if (updatedSession != null) { + sessions[session.session] = updatedSession + } + } + private fun setupConnectionTimeoutCheck(session: WCSession) { scope.launch { delay(20_000) @@ -81,7 +92,7 @@ class WalletConnectManager { client = WCClient(httpClient = okHttpClient), session = session.session, peerMeta = session.peerMeta, - wallet = session.wallet + wallet = session.wallet, ) .also { setListeners(it.client) @@ -96,11 +107,12 @@ class WalletConnectManager { val activeData = sessions[session] ?: return removeSimilarSessions(activeData) - val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey - val accounts = listOf(Blockchain.Ethereum.makeAddresses(key).first().value) + val key = activeData.wallet.derivedPublicKey ?: activeData.wallet.walletPublicKey ?: return + val blockchain = activeData.wallet.getBlockchainForSession() + val accounts = listOf(blockchain.makeAddresses(key).first().value) val approved = activeData.client.approveSession( accounts = accounts, - chainId = activeData.wallet.chainId + chainId = blockchain.getChainId() ?: Blockchain.Ethereum.getChainId()!! ) if (approved) { val walletConnectSession = WalletConnectSession( @@ -111,15 +123,18 @@ class WalletConnectManager { peerMeta = activeData.peerMeta!! ) walletConnectRepository.saveSession(walletConnectSession) - store.dispatchOnMain(WalletConnectAction.ApproveSession.Success( - walletConnectSession)) + store.dispatchOnMain( + WalletConnectAction.ApproveSession.Success( + walletConnectSession + ) + ) } } fun removeSimilarSessions(activeData: WalletConnectActiveData) { val sessionsToRemove = sessions.filter { - it.value.wallet.walletPublicKey == activeData.wallet.walletPublicKey + it.value.wallet.walletPublicKey?.equals(activeData.wallet.walletPublicKey) == true && it.value.peerMeta?.url == activeData.peerMeta?.url && it.value.session != activeData.session } @@ -146,9 +161,10 @@ class WalletConnectManager { } private fun onSessionClosed(session: WCSession) { - FirebaseAnalyticsHandler.logWcEvent( - FirebaseAnalyticsHandler.WcAnalyticsEvent.Session( - FirebaseAnalyticsHandler.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url) + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Session( + Analytics.WcSessionEvent.Disconnect, sessions[session]?.peerMeta?.url + ) ) sessions.remove(session) walletConnectRepository.removeSession(session) @@ -170,17 +186,23 @@ class WalletConnectManager { type = type ).guard { sessions[session.session] = activeData.copy(transactionData = null) - store.dispatchOnMain(WalletConnectAction.RejectRequest( - session.session, - id - )) + store.dispatchOnMain( + WalletConnectAction.RejectRequest( + session.session, + id + ) + ) return@launch } sessions[session.session] = activeData.copy(transactionData = data) - store.dispatchOnMain(WalletConnectAction.SetDataToSend(data)) - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.RequestTransaction( - data.dialogData))) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.RequestTransaction( + data.dialogData + ) + ) + ) } } @@ -190,13 +212,34 @@ class WalletConnectManager { scope.launch { val hash = WalletConnectSdkHelper().completeTransaction(data).guard { sessions[data.session.session] = activeData.copy(transactionData = null) - store.dispatchOnMain(WalletConnectAction.RejectRequest(data.session.session, - data.id - )) + store.dispatchOnMain( + WalletConnectAction.RejectRequest( + data.session.session, + data.id + ) + ) return@launch } - acceptRequest(data.session.session, data.id, hash) - sessions[data.session.session] = activeData.copy(transactionData = null) + acceptRequest(session, data.id, hash) + sessions[session] = activeData.copy(transactionData = null) + } + } + + fun signBnb( + id: Long, data: ByteArray, sessionData: WCSession, + ) { + val activeData = sessions[sessionData] ?: return + scope.launch { + val hash = WalletConnectSdkHelper().signBnbTransaction(data, activeData).guard { + store.dispatchOnMain( + WalletConnectAction.RejectRequest( + sessionData, + id + ) + ) + return@launch + } + acceptRequest(sessionData, id, hash) } } @@ -211,9 +254,13 @@ class WalletConnectManager { message = message, session = session, id = id ) sessions[session.session] = activeData.copy(personalSignData = data) - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.PersonalSign( - data.dialogData - ))) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.PersonalSign( + data.dialogData + ) + ) + ) } } @@ -224,9 +271,12 @@ class WalletConnectManager { val hash = WalletConnectSdkHelper().signPersonalMessage(data.hash, activeData.wallet) .guard { sessions[data.session.session] = activeData.copy(transactionData = null) - store.dispatchOnMain(WalletConnectAction.RejectRequest(data.session.session, - data.id - )) + store.dispatchOnMain( + WalletConnectAction.RejectRequest( + data.session.session, + data.id + ) + ) return@launch } sessions[session] = activeData.copy(personalSignData = data) @@ -245,12 +295,17 @@ class WalletConnectManager { sessions[session] = data val sessionData = data.toWalletConnectSession() sessionData?.let { - store.dispatchOnMain(WalletConnectAction.AcceptOpeningSession( - sessionData)) + store.dispatchOnMain( + WalletConnectAction.AcceptOpeningSession( + session = sessionData, + chainId = client.chainId?.toIntOrNull() + ) + ) } - FirebaseAnalyticsHandler.logWcEvent( - FirebaseAnalyticsHandler.WcAnalyticsEvent.Session( - FirebaseAnalyticsHandler.WcSessionEvent.Connect, peer.url) + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Session( + Analytics.WcSessionEvent.Connect, peer.url + ) ) } } @@ -261,56 +316,113 @@ class WalletConnectManager { } client.onEthSendTransaction = { id: Long, transaction: WCEthereumTransaction -> Timber.d("onEthSendTransaction: $transaction") - FirebaseAnalyticsHandler.logWcEvent( - FirebaseAnalyticsHandler.WcAnalyticsEvent.Action( - FirebaseAnalyticsHandler.WcAction.SendTransaction + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Action( + Analytics.WcAction.SendTransaction ) ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest( - transaction = transaction, - session = sessionData, - id = id, - type = WcTransactionType.EthSendTransaction - )) + store.dispatchOnMain( + WalletConnectAction.HandleTransactionRequest( + transaction = transaction, + session = sessionData, + id = id, + type = WcTransactionType.EthSendTransaction + ) + ) } } client.onEthSignTransaction = { id: Long, transaction: WCEthereumTransaction -> Timber.d("onEthSignTransaction: $transaction") - FirebaseAnalyticsHandler.logWcEvent( - FirebaseAnalyticsHandler.WcAnalyticsEvent.Action( - FirebaseAnalyticsHandler.WcAction.SignTransaction + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Action( + Analytics.WcAction.SignTransaction ) ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain(WalletConnectAction.HandleTransactionRequest( - transaction = transaction, - session = sessionData, - id = id, - type = WcTransactionType.EthSignTransaction - )) + store.dispatchOnMain( + WalletConnectAction.HandleTransactionRequest( + transaction = transaction, + session = sessionData, + id = id, + type = WcTransactionType.EthSignTransaction + ) + ) } } client.onEthSign = { id: Long, message: WCEthereumSignMessage -> Timber.d("onEthSign: $message") - FirebaseAnalyticsHandler.logWcEvent( - FirebaseAnalyticsHandler.WcAnalyticsEvent.Action( - FirebaseAnalyticsHandler.WcAction.PersonalSign + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Action( + Analytics.WcAction.PersonalSign ) ) sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> - store.dispatchOnMain(WalletConnectAction.HandlePersonalSignRequest( - message, - sessionData, - id)) + store.dispatchOnMain( + WalletConnectAction.HandlePersonalSignRequest( + message, + sessionData, + id + ) + ) } } + + + client.onBnbCancel = { id: Long, order: WCBinanceCancelOrder -> + + } + client.onBnbTrade = { id: Long, order: WCBinanceTradeOrder -> + sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + store.dispatchOnMain( + WalletConnectAction.BinanceTransaction.Trade( + id = id, order = order, sessionData = sessionData + ) + ) + } + } + client.onBnbTransfer = { id: Long, order: WCBinanceTransferOrder -> + sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + store.dispatchOnMain( + WalletConnectAction.BinanceTransaction.Transfer( + id = id, order = order, sessionData = sessionData + ) + ) + } + } + client.onBnbTxConfirm = { id: Long, order: WCBinanceTxConfirmParam -> + // send empty approve request if status is OK + if (order.ok) client.approveRequest(id, "") + } client.onDisconnect = { code: Int, reason: String -> val session = client.session if (session != null) { onSessionClosed(session) } } + client.onCustomRequest = { id: Long, data: String -> + Timber.d("Custom Request") + Timber.d(data) + + val message = EthSignHelper.tryToParseEthTypedMessage(data) + if (message != null) { + Timber.d("onEthSign_v4: $message") + store.state.globalState.analyticsHandlers?.logWcEvent( + Analytics.WcAnalyticsEvent.Action( + Analytics.WcAction.PersonalSign + ) + ) + sessions[client.session]?.toWalletConnectSession()?.let { sessionData -> + store.dispatchOnMain( + WalletConnectAction.HandlePersonalSignRequest( + message, + sessionData, + id + ) + ) + } + } + } } companion object { diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt new file mode 100644 index 0000000000..4907e8d40c --- /dev/null +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectNetworkUtils.kt @@ -0,0 +1,39 @@ +package com.tangem.tap.domain.walletconnect + +import com.tangem.blockchain.common.Blockchain +import com.trustwallet.walletconnect.models.WCPeerMeta + +class WalletConnectNetworkUtils { + + companion object { + + fun parseBlockchain( + chainId: Int?, + peer: WCPeerMeta, + isTestNet: Boolean? = null + ): Blockchain? { + return when { + chainId != null -> { + Blockchain.fromChainId(chainId) + } + peer.url.contains("matic.network") || peer.name == "Polygon" -> { + Blockchain.Polygon + } + peer.url.contains("binance.org") || peer.name.contains("Binance") -> { + if (peer.icons.firstOrNull()?.contains("testnet") == true) { + Blockchain.BinanceTestnet + } else { + Blockchain.Binance + } + } + peer.name.contains("BSC") -> { + Blockchain.BSC + } + else -> { + if (isTestNet == true) Blockchain.EthereumTestnet else Blockchain.Ethereum + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt index a4654d6429..67dab3645d 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectRepository.kt @@ -88,7 +88,6 @@ data class SessionDao( session = session.session, peerMeta = session.peerMeta ) - } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 85ef0eaac6..630176edc6 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -10,9 +10,10 @@ import com.tangem.blockchain.extensions.* import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toHexString import com.tangem.crypto.CryptoUtils import com.tangem.operations.sign.SignHashCommand -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.extensions.toFormattedString import com.tangem.tap.features.details.redux.walletconnect.* import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData @@ -33,20 +34,8 @@ class WalletConnectSdkHelper { id: Long, type: WcTransactionType, ): WcTransactionData? { - val factory = store.state.globalState.tapWalletManager.walletManagerFactory - val publicKey = Wallet.PublicKey( - session.wallet.walletPublicKey, - session.wallet.derivedPublicKey, - session.wallet.derivationPath, - ) - val walletManager = factory.makeEthereumWalletManager( - session.wallet.cardId, - publicKey, - emptyList(), - isTestNet = session.wallet.isTestNet - ) ?: return null - + val walletManager = getWalletManager(session) ?: return null try { walletManager.update() } catch (exception: Exception) { @@ -54,15 +43,14 @@ class WalletConnectSdkHelper { return null } - val blockchain = walletManager.wallet.blockchain - + val wallet = walletManager.wallet val balance = - walletManager.wallet.amounts[AmountType.Coin]?.value ?: return null + wallet.amounts[AmountType.Coin]?.value ?: return null val gas = transaction.gas?.hexToBigDecimal() ?: transaction.gasLimit?.hexToBigDecimal() ?: return null - val decimals = blockchain.decimals() + val decimals = wallet.blockchain.decimals() val value = (transaction.value ?: "0").hexToBigDecimal() ?.movePointLeft(decimals) ?: return null @@ -82,8 +70,8 @@ class WalletConnectSdkHelper { val total = value + fee val transactionData = TransactionData( - amount = Amount(value, blockchain), - fee = Amount(fee, blockchain), + amount = Amount(value, wallet.blockchain), + fee = Amount(fee, wallet.blockchain), sourceAddress = transaction.from, destinationAddress = transaction.to!!, extras = EthereumTransactionExtras( @@ -115,6 +103,21 @@ class WalletConnectSdkHelper { ) } + private fun getWalletManager(session: WalletConnectSession): WalletManager? { + val factory = store.state.globalState.tapWalletManager.walletManagerFactory + val publicKey = Wallet.PublicKey( + session.wallet.walletPublicKey ?: return null, + session.wallet.derivedPublicKey, + session.wallet.derivationPath, + ) + val blockchain = session.wallet.getBlockchainForSession() + return factory.makeWalletManager( + session.wallet.cardId, + blockchain, + publicKey + ) + } + suspend fun completeTransaction(data: WcTransactionData): String? { return when (data.type) { WcTransactionType.EthSendTransaction -> sendTransaction(data) @@ -133,9 +136,9 @@ class WalletConnectSdkHelper { } is SimpleResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + store.state.globalState.analyticsHandlers?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.WalletConnectTransaction, + Analytics.ActionToLog.WalletConnectTransaction, ) } Timber.e(result.error) @@ -164,9 +167,38 @@ class WalletConnectSdkHelper { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + store.state.globalState.analyticsHandlers?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign, + Analytics.ActionToLog.WalletConnectSign, + ) + } + Timber.e(result.error.customMessage) + null + } + } + } + + suspend fun signBnbTransaction(data: ByteArray, session: WalletConnectActiveData): String? { + val command = SignHashCommand( + hash = data, + walletPublicKey = session.wallet.walletPublicKey ?: return null, + derivationPath = session.wallet.derivationPath + ) + val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message()) + return when (result) { + is CompletionResult.Success -> { + val key = session.wallet.derivedPublicKey + ?: session.wallet.walletPublicKey + getBnbResultString( + key.toHexString(), + result.data.signature.toHexString() + ) + } + is CompletionResult.Failure -> { + (result.error as? TangemSdkError)?.let { error -> + store.state.globalState.analyticsHandlers?.logCardSdkError( + error, + Analytics.ActionToLog.WalletConnectTransaction, ) } Timber.e(result.error.customMessage) @@ -180,8 +212,15 @@ class WalletConnectSdkHelper { session: WalletConnectSession, id: Long, ): WcPersonalSignData { - val messageData = message.data.removePrefix(HEX_PREFIX).hexToBytes() - val messageString = message.data.hexToAscii() ?: message.data + val messageData = + try { + message.data.removePrefix(HEX_PREFIX).hexToBytes() + } catch (exception: Exception) { + message.data.asciiToHex()!!.hexToBytes() + } + val messageString = message.data.hexToAscii() + ?: EthSignHelper.tryToParseEthTypedMessageString(message.data) + ?: message.data val prefixData = (ETH_MESSAGE_PREFIX + messageData.size.toString()).toByteArray() val hashToSign = (prefixData + messageData).toKeccak() @@ -203,29 +242,40 @@ class WalletConnectSdkHelper { } private fun String.hexToAscii(): String? { - return removePrefix(HEX_PREFIX).hexToBytes() - .map { - val char = it.toInt().toChar() - if (char.isAscii()) char else return null - } - .joinToString("") + return try { + removePrefix(HEX_PREFIX).hexToBytes() + .map { + val char = it.toInt().toChar() + if (char.isAscii()) char else return null + } + .joinToString("") + } catch (exception: Exception) { + return null + } + } + + private fun String.asciiToHex(): String? { + return map { + if (!it.isAscii()) return null + Integer.toHexString(it.code) + }.joinToString("") } suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? { val key = wallet.derivedPublicKey ?: wallet.walletPublicKey - val command = SignHashCommand(hashToSign, wallet.walletPublicKey, wallet.derivationPath) + val command = SignHashCommand(hashToSign, wallet.walletPublicKey!!, wallet.derivationPath) return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) { is CompletionResult.Success -> { val hash = result.data.signature return EthereumUtils.prepareSignedMessageData( - hash, hashToSign, CryptoUtils.decompressPublicKey(key) + hash, hashToSign, CryptoUtils.decompressPublicKey(key!!) ) } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + store.state.globalState.analyticsHandlers?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.WalletConnectSign, + Analytics.ActionToLog.WalletConnectSign, ) } Timber.e(result.error.customMessage) @@ -237,5 +287,8 @@ class WalletConnectSdkHelper { companion object { private const val ETH_MESSAGE_PREFIX = "\u0019Ethereum Signed Message:\n" private const val HEX_PREFIX = "0x" + fun getBnbResultString(publicKey: String, signature: String): String { + return "{\"signature\":\"$signature\",\"publicKey\":\"$publicKey\"}" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt index 2a6d764f4c..dfcf2c03f3 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/DetailsMiddleware.kt @@ -5,7 +5,8 @@ import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result import com.tangem.operations.pins.CheckUserCodesResponse -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.analytics.Analytics +import com.tangem.tap.common.analytics.AnalyticsParam import com.tangem.tap.common.extensions.dispatchNotification import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppState @@ -118,9 +119,9 @@ class DetailsMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + store.state.globalState.analyticsHandlers?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.PurgeWallet, + Analytics.ActionToLog.PurgeWallet, card = store.state.detailsState.scanResponse?.card ) } @@ -206,11 +207,11 @@ class DetailsMiddleware { } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + store.state.globalState.analyticsHandlers?.logCardSdkError( error = error, - actionToLog = FirebaseAnalyticsHandler.ActionToLog.ChangeSecOptions, + actionToLog = Analytics.ActionToLog.ChangeSecOptions, parameters = mapOf( - FirebaseAnalyticsHandler.AnalyticsParam.NEW_SECURITY_OPTION to + AnalyticsParam.NEW_SECURITY_OPTION to (selectedOption?.name ?: "") ), card = store.state.detailsState.scanResponse?.card diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt index 16a3f638a4..f5f509cc74 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectAction.kt @@ -2,7 +2,10 @@ package com.tangem.tap.features.details.redux.walletconnect import android.app.Activity import com.tangem.tap.common.redux.NotificationAction +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.wallet.R +import com.trustwallet.walletconnect.models.binance.WCBinanceTradeOrder +import com.trustwallet.walletconnect.models.binance.WCBinanceTransferOrder import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction import com.trustwallet.walletconnect.models.session.WCSession @@ -27,13 +30,16 @@ sealed class WalletConnectAction : Action { data class OpenSession( val wcUri: String, val wallet: WalletForSession, + val scanResponse: ScanResponse ) : WalletConnectAction() object RefuseOpeningSession : WalletConnectAction() data class OpeningSessionTimeout(val session: WCSession) : WalletConnectAction() - data class AcceptOpeningSession(val session: WalletConnectSession) : WalletConnectAction() + data class AcceptOpeningSession( + val session: WalletConnectSession, val chainId: Int?, + ) : WalletConnectAction() data class ApproveSession( val session: WCSession, @@ -79,4 +85,20 @@ sealed class WalletConnectAction : Action { object NotifyCameraPermissionIsRequired : WalletConnectAction(), NotificationAction { override val messageResource = R.string.common_camera_denied_alert_message } + + object BinanceTransaction : WalletConnectAction() { + data class Trade( + val id: Long, val order: WCBinanceTradeOrder, + val sessionData: WalletConnectSession, + ) : WalletConnectAction() + + data class Transfer( + val id: Long, val order: WCBinanceTransferOrder, + val sessionData: WalletConnectSession, + ) : WalletConnectAction() + + data class Sign( + val id: Long, val data: ByteArray, val sessionData: WCSession, + ) : WalletConnectAction() + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index 2c1aab1596..92c6d69072 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.details.redux.walletconnect import com.tangem.blockchain.common.* -import com.tangem.common.extensions.guard import com.tangem.tap.* import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.getFromClipboard @@ -13,10 +12,14 @@ import com.tangem.tap.domain.TapWorkarounds.isTestCard import com.tangem.tap.domain.extensions.makeWalletManagerForApp import com.tangem.tap.domain.isMultiwalletAllowed import com.tangem.tap.domain.tasks.product.ScanResponse +import com.tangem.tap.domain.walletconnect.BnbHelper import com.tangem.tap.domain.walletconnect.WalletConnectManager +import com.tangem.tap.domain.walletconnect.WalletConnectNetworkUtils import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.wallet.R +import com.trustwallet.walletconnect.models.WCPeerMeta import org.rekotlin.Middleware +import timber.log.Timber class WalletConnectMiddleware { private val walletConnectManager = WalletConnectManager() @@ -48,8 +51,13 @@ class WalletConnectMiddleware { } is WalletConnectAction.ShowClipboardOrScanQrDialog -> { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.ClipboardOrScanQr( - action.wcUri))) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.ClipboardOrScanQr( + action.wcUri + ) + ) + ) } is WalletConnectAction.ScanCard -> { @@ -78,14 +86,23 @@ class WalletConnectMiddleware { } is WalletConnectAction.RefuseOpeningSession -> { - store.dispatch(GlobalAction.ShowDialog( - WalletConnectDialog.OpeningSessionRejected - )) + store.dispatch( + GlobalAction.ShowDialog( + WalletConnectDialog.OpeningSessionRejected + ) + ) } is WalletConnectAction.AcceptOpeningSession -> { - store.dispatchOnMain(GlobalAction.ShowDialog(WalletConnectDialog.ApproveWcSession( - action.session))) + generateWalletKeys(action.session, action.chainId) + + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.ApproveWcSession( + action.session + ) + ) + ) } is WalletConnectAction.ApproveSession -> { @@ -120,6 +137,39 @@ class WalletConnectMiddleware { is WalletConnectAction.SignMessage -> { walletConnectManager.sendSignedMessage(action.session) } + is WalletConnectAction.BinanceTransaction.Trade -> { + val messageData = BnbHelper.createMessageData(action.order) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.BnbTransactionDialog( + data = messageData, + session = action.sessionData.session, + sessionId = action.id, + cardId = action.sessionData.wallet.cardId, + dAppName = action.sessionData.peerMeta.name + ) + ) + ) + } + is WalletConnectAction.BinanceTransaction.Transfer -> { + val messageData = BnbHelper.createMessageData(action.order) + store.dispatchOnMain( + GlobalAction.ShowDialog( + WalletConnectDialog.BnbTransactionDialog( + data = messageData, + session = action.sessionData.session, + sessionId = action.id, + cardId = action.sessionData.wallet.cardId, + dAppName = action.sessionData.peerMeta.name + ) + ) + ) + } + is WalletConnectAction.BinanceTransaction.Sign -> { + walletConnectManager.signBnb( + action.id, action.data, action.sessionData + ) + } } next(action) } @@ -134,60 +184,79 @@ class WalletConnectMiddleware { return@ScanCard } - val walletManager = getWalletManager(scanResponse).guard { - store.dispatchOnMain(WalletConnectAction.UnsupportedCard) - return@ScanCard - } - - val wallet = walletManager.wallet - val derivedKey = if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { - null - } else { - walletManager.wallet.publicKey.blockchainKey - } - store.dispatchOnMain(WalletConnectAction.OpenSession( - wcUri = wcUri, - wallet = WalletForSession( - card.cardId, - wallet.publicKey.seedKey, - derivedKey, - wallet.publicKey.derivationPath, - card.isTestCard - ), - )) + store.dispatchOnMain( + WalletConnectAction.OpenSession( + wcUri = wcUri, + wallet = WalletForSession( + scanResponse.card.cardId, + null, + null, + null, + scanResponse.card.isTestCard + ), + scanResponse = scanResponse + ) + ) }, { store.dispatchOnMain(WalletConnectAction.FailureEstablishingSession(null)) }, R.string.wallet_connect_scan_card_message)) } - private fun getWalletManager(scanResponse: ScanResponse): WalletManager? { + private fun generateWalletKeys(session: WalletConnectSession, chainId: Int?) { + val scanResponse = store.state.walletConnectState.scanResponse ?: return + val walletManager = getWalletManager(scanResponse, session.peerMeta, chainId) ?: return + + val wallet = walletManager.wallet + val derivedKey = + if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) { + null + } else { + walletManager.wallet.publicKey.blockchainKey + } + val updatedWallet = session.wallet.copy( + walletPublicKey = wallet.publicKey.seedKey, + derivedPublicKey = derivedKey, + derivationPath = wallet.publicKey.derivationPath, + blockchain = wallet.blockchain + ) + val updatedSession = session.copy(wallet = updatedWallet) + walletConnectManager.updateSession(updatedSession) + } + + private fun getWalletManager( + scanResponse: ScanResponse, peer: WCPeerMeta, chainId: Int? + ): WalletManager? { val card = scanResponse.card val factory = store.state.globalState.tapWalletManager.walletManagerFactory - val wcBlockchain = if (scanResponse.card.isTestCard) { - Blockchain.EthereumTestnet - } else { - Blockchain.Ethereum - } + val blockchain = WalletConnectNetworkUtils.parseBlockchain( + chainId = chainId, peer = peer, isTestNet = scanResponse.card.isTestCard + ) ?: if (scanResponse.card.isTestCard) Blockchain.EthereumTestnet else Blockchain.Ethereum + + Timber.d(blockchain.fullName) + return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) { - store.state.walletState.getWalletManager(wcBlockchain) - ?: factory.makeWalletManagerForApp(scanResponse, wcBlockchain) + store.state.walletState.getWalletManager(blockchain) + ?: factory.makeWalletManagerForApp(scanResponse, blockchain) ?.also { walletManager -> store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager)) store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain)) } } else { + val walletManager = factory.makeWalletManagerForApp(scanResponse, blockchain) if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains( - wcBlockchain) == true + blockchain + ) != true ) { - factory.makeWalletManagerForApp(scanResponse, wcBlockchain) - } else { - factory.makeWalletManagerForApp(scanResponse, wcBlockchain) - ?.also { - currenciesRepository.saveAddedBlockchain(card.cardId, wcBlockchain) - } + walletManager?.let { + currenciesRepository.saveAddedBlockchain( + card.cardId, + blockchain + ) + } } + return walletManager } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt index c8b6f3b1b7..c96f72fc9e 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectReducer.kt @@ -16,6 +16,9 @@ class WalletConnectReducer { sessions = state.sessions + action.session ) } + is WalletConnectAction.OpenSession -> { + state.copy(scanResponse = action.scanResponse) + } is WalletConnectAction.SetSessionsRestored -> WalletConnectState(sessions = action.sessions) diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt index 2757d8cea9..b00b7d9ff9 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectState.kt @@ -1,10 +1,12 @@ package com.tangem.tap.features.details.redux.walletconnect import com.squareup.moshi.JsonClass +import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.WalletManager import com.tangem.common.hdWallet.DerivationPath import com.tangem.tap.common.redux.StateDialog +import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData import com.trustwallet.walletconnect.models.WCPeerMeta @@ -13,6 +15,7 @@ import com.trustwallet.walletconnect.models.session.WCSession data class WalletConnectState( val loading: Boolean = false, val sessions: List = listOf(), + val scanResponse: ScanResponse? = null ) data class WalletConnectSession( @@ -21,18 +24,58 @@ data class WalletConnectSession( val wallet: WalletForSession, val session: WCSession, val peerMeta: WCPeerMeta, -) +) { + fun getAddress(): String? { + val key = wallet.derivedPublicKey ?: wallet.walletPublicKey ?: return null + return wallet.blockchain?.makeAddresses(key)?.first()?.value + } +} @JsonClass(generateAdapter = true) data class WalletForSession( val cardId: String, - val walletPublicKey: ByteArray, + val walletPublicKey: ByteArray?, val derivedPublicKey: ByteArray?, val derivationPath: DerivationPath?, val isTestNet: Boolean = false, + val blockchain: Blockchain? = if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum ) { - val chainId - get() = if (isTestNet) 4 else 1 + + fun getBlockchainForSession(): Blockchain { + return blockchain ?: if (isTestNet) Blockchain.EthereumTestnet else Blockchain.Ethereum + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as WalletForSession + + if (cardId != other.cardId) return false + if (walletPublicKey != null) { + if (other.walletPublicKey == null) return false + if (!walletPublicKey.contentEquals(other.walletPublicKey)) return false + } else if (other.walletPublicKey != null) return false + if (derivedPublicKey != null) { + if (other.derivedPublicKey == null) return false + if (!derivedPublicKey.contentEquals(other.derivedPublicKey)) return false + } else if (other.derivedPublicKey != null) return false + if (derivationPath != other.derivationPath) return false + if (isTestNet != other.isTestNet) return false + if (blockchain != other.blockchain) return false + + return true + } + + override fun hashCode(): Int { + var result = cardId.hashCode() + result = 31 * result + (walletPublicKey?.contentHashCode() ?: 0) + result = 31 * result + (derivedPublicKey?.contentHashCode() ?: 0) + result = 31 * result + (derivationPath?.hashCode() ?: 0) + result = 31 * result + isTestNet.hashCode() + result = 31 * result + (blockchain?.hashCode() ?: 0) + return result + } } @@ -46,6 +89,12 @@ sealed class WalletConnectDialog : StateDialog { WalletConnectDialog() data class PersonalSign(val data: PersonalSignDialogData) : WalletConnectDialog() + data class BnbTransactionDialog(val data: BinanceMessageData, + val session: WCSession, + val sessionId: Long, + val cardId: String, + val dAppName: String + ) : WalletConnectDialog() } data class WcTransactionData( @@ -59,7 +108,7 @@ data class WcTransactionData( enum class WcTransactionType { EthSignTransaction, - EthSendTransaction, + EthSendTransaction } data class WcPersonalSignData( @@ -68,4 +117,29 @@ data class WcPersonalSignData( val id: Long, val dialogData: PersonalSignDialogData, - ) \ No newline at end of file + ) + +sealed class BinanceMessageData( + val address: String, + val data: ByteArray, +) { + class Trade( + val tradeData: List, + address: String, + data: ByteArray, + ) : BinanceMessageData(address, data) + + class Transfer( + val outputAddress: String, + val amount: String, + address: String, + data: ByteArray, + ) : BinanceMessageData(address, data) +} + +data class TradeData( + val price: String, + val quantity: String, + val amount: String, + val symbol: String +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt index 74d3f40fd9..acb76789ba 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/DetailsFragment.kt @@ -73,7 +73,10 @@ class DetailsFragment : Fragment(R.layout.fragment_details), StoreSubscriber data.tradeData.map { + context.getString( + R.string.wallet_connect_bnb_trade_order_message, + it.symbol, + it.price, + it.quantity, + it.amount + ) + }.joinToString ( "\n\n" ) + is BinanceMessageData.Transfer -> context.getString( + R.string.wallet_connect_bnb_transaction_message, + data.address, + data.outputAddress, + data.amount + ) + } + + val fullMessage = context.getString( + R.string.wallet_connect_bnb_sign_message, + dAppName, cardId, message + ) + val positiveButtonTitle = context.getText(R.string.common_sign) + + return AlertDialog.Builder(context).apply { + setTitle(context.getString(R.string.wallet_connect)) + setMessage(fullMessage) + setPositiveButton(positiveButtonTitle) { _, _ -> + store.dispatch(WalletConnectAction.BinanceTransaction.Sign( + id = sessionId, + data = data.data, + sessionData = session + )) + } + setNegativeButton(context.getText(R.string.common_reject)) { _, _ -> + store.dispatch(WalletConnectAction.RejectRequest(session, sessionId)) + } + setOnDismissListener { + store.dispatch(GlobalAction.HideDialog) + } + }.create() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt index 341aa3e64b..467929a848 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeMiddleware.kt @@ -1,5 +1,8 @@ package com.tangem.tap.features.home.redux +import com.tangem.tap.common.analytics.AnalyticsEvent +import com.tangem.tap.common.analytics.AnalyticsParam +import com.tangem.tap.common.analytics.GetCardSourceParams import com.tangem.tap.common.entities.IndeterminateProgressButton import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.onCardScanned @@ -46,7 +49,13 @@ private val homeMiddleware: Middleware = { dispatch, state -> } } is HomeAction.ReadCard -> handleReadCard() - is HomeAction.GoToShop -> store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) + is HomeAction.GoToShop -> { + store.dispatchOpenUrl(HomeMiddleware.CARD_SHOP_URI) + store.state.globalState.analyticsHandlers?.triggerEvent( + event = AnalyticsEvent.GET_CARD, + params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.WELCOME.param) + ) + } } next(action) } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt index 3aa671c5c3..9c3037a33c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -37,25 +37,6 @@ class OnboardingManager( return scanResponse.card.getOrLoadCardArtworkUrl(cardInfo) } -// suspend fun loadArtwork(): Artwork { -// cardInfo = OnlineCardVerifier().getCardInfo(scanResponse.card.cardId, scanResponse.card.cardPublicKey) -// return loadArtwork(scanResponse.card.getOrLoadCardArtworkUrl(cardInfo)) -// } -// -// suspend fun loadArtwork(url: String): Artwork { -// return withContext(Dispatchers.Main) { -// suspendCoroutine { continuation -> -// UrlBitmapLoader().loadBitmap(url) { -// val result = when (it) { -// is Result.Success -> Artwork(url, it.data) -// is Result.Failure -> Artwork(url, null) -// } -// continuation.resume(result) -// } -// } -// } -// } - suspend fun updateBalance(walletManager: WalletManager): OnboardingWalletBalance { val balance = when (val result = walletManager.safeUpdate()) { is Result.Success -> { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt index f69e59381f..f32decfddd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsMiddleware.kt @@ -130,7 +130,11 @@ private fun handle(action: Action, dispatch: DispatchFunction) { } } is TwinCardsAction.Wallet.LaunchFirstStep -> { - val manager = TwinCardsManager(getScanResponse().card, action.reader) + val manager = TwinCardsManager( + card = getScanResponse().card, + assetReader = action.reader, + analyticsHandler = store.state.globalState.analyticsHandlers + ) store.dispatch(TwinCardsAction.CardsManager.Set(manager)) scope.launch { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt index 33eaf7a983..12e87f2a70 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/ui/TwinsCardsFragment.kt @@ -12,8 +12,8 @@ import com.squareup.picasso.Picasso import com.tangem.Message import com.tangem.blockchain.common.Blockchain import com.tangem.common.extensions.VoidCallback +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.extensions.* -import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.common.redux.navigation.ShareElement diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index 9bfdaaf381..57ede6fd8a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -1,7 +1,6 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap -import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.Action sealed class OnboardingWalletAction : Action { @@ -14,7 +13,7 @@ sealed class OnboardingWalletAction : Action { object ProceedBackup : OnboardingWalletAction() object LoadArtwork : OnboardingWalletAction() - class SetArtworkUrl(val artwork: Artwork) : OnboardingWalletAction() + class SetArtworkUrl(val artworkUrl: String?) : OnboardingWalletAction() object OnBackPressed : OnboardingWalletAction() } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 1816465f6e..29cdd561b2 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -4,6 +4,9 @@ import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.operations.backup.BackupService import com.tangem.tap.* +import com.tangem.tap.common.analytics.AnalyticsEvent +import com.tangem.tap.common.analytics.AnalyticsParam +import com.tangem.tap.common.analytics.GetCardSourceParams import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.dispatchOpenUrl import com.tangem.tap.common.extensions.withMainContext @@ -14,8 +17,8 @@ import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.extensions.hasWallets import com.tangem.tap.domain.tasks.product.ScanResponse import com.tangem.tap.features.home.redux.HomeAction -import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL +import com.tangem.tap.features.wallet.redux.Artwork import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -61,11 +64,16 @@ private fun handleWalletAction(action: Action) { } store.dispatch(action) } - is OnboardingNoteAction.LoadCardArtwork -> { -// scope.launch { -// val artwork = onboardingManager.loadArtwork() -// store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(artwork)) -// } + is OnboardingWalletAction.LoadArtwork -> { + scope.launch { + val artwork = onboardingManager?.loadArtworkUrl() + val cardArtwork = if (artwork == Artwork.DEFAULT_IMG_URL) { + null + } else { + artwork + } + store.dispatchOnMain(OnboardingWalletAction.SetArtworkUrl(cardArtwork)) + } } is OnboardingWalletAction.CreateWallet -> { scope.launch { @@ -207,7 +215,13 @@ private fun handleBackupAction(action: BackupAction) { } } is BackupAction.GoToShop -> { - store.dispatchOpenUrl(BUY_WALLET_URL) + backupState.buyAdditionalCardsUrl?.let { url -> + store.dispatchOpenUrl(url) + store.state.globalState.analyticsHandlers?.triggerEvent( + event = AnalyticsEvent.GET_CARD, + params = mapOf(AnalyticsParam.SOURCE.param to GetCardSourceParams.ONBOARDING.param) + ) + } } is BackupAction.FinishAddingBackupCards -> { if (backupService.addedBackupCardsCount == 1) { @@ -223,11 +237,13 @@ private fun handleBackupAction(action: BackupAction) { AccessCodeError.CodeTooShort )) } else { + store.dispatch(BackupAction.SetAccessCodeError(null)) store.dispatch(BackupAction.SaveFirstAccessCode(action.accessCode)) } } is BackupAction.SaveAccessCodeConfirmation -> { if (action.accessCodeConfirmation == backupState.accessCode) { + store.dispatch(BackupAction.SetAccessCodeError(null)) backupService.setAccessCode(action.accessCodeConfirmation) store.dispatch(BackupAction.PrepareToWritePrimaryCard) } else { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 89542c532e..f46c1bf0ff 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -24,7 +24,7 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet OnboardingWalletState(step = OnboardingWalletStep.CreateWallet) is OnboardingWalletAction.ProceedBackup -> state.copy(step = OnboardingWalletStep.Backup) is OnboardingWalletAction.SetArtworkUrl -> { - state.copy(artwork = action.artwork) + state.copy(cardArtworkUrl = action.artworkUrl) } is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done) else -> state @@ -60,7 +60,7 @@ class BackupReducer { state.copy(backupStep = BackupStep.SetAccessCode) } BackupAction.ShowEnterAccessCodeScreen -> { - state.copy(backupStep = BackupStep.EnterAccessCode) + state.copy(backupStep = BackupStep.EnterAccessCode, accessCodeError = null) } is BackupAction.SaveFirstAccessCode -> { state.copy( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index a048261a6d..7aaa0fe88f 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap import com.tangem.tap.common.redux.StateDialog -import com.tangem.tap.features.wallet.redux.Artwork import org.rekotlin.StateType /** @@ -11,7 +10,7 @@ import org.rekotlin.StateType data class OnboardingWalletState( val step: OnboardingWalletStep = OnboardingWalletStep.None, val backupState: BackupState = BackupState(), - val artwork: Artwork? = null, + val cardArtworkUrl: String? = null, val showConfetti: Boolean = false, ) : StateType { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt index e4de53c9a4..1b4ecba2a7 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/BackupCardsWidget.kt @@ -6,9 +6,9 @@ import android.animation.PropertyValuesHolder import android.view.View import android.widget.ImageView import androidx.core.animation.doOnEnd -import com.tangem.tap.common.leapfrogWidget.LeapView -import com.tangem.tap.common.leapfrogWidget.LeapViewState -import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapView +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapViewState +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget class BackupCardsWidget( val leapfrogWidget: LeapfrogWidget, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 5a1ccf6684..27fe4c8eb7 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -2,23 +2,22 @@ package com.tangem.tap.features.onboarding.products.wallet.ui import android.os.Bundle import android.view.* +import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.transition.TransitionInflater import androidx.transition.TransitionManager import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayoutMediator +import com.squareup.picasso.Picasso import com.tangem.common.CardIdFormatter import com.tangem.common.core.CardIdDisplayFormat -import com.tangem.tap.common.extensions.dispatchOpenUrl +import com.tangem.tangem_sdk_new.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.leapfrogWidget.LeapfrogWidget -import com.tangem.tap.common.postUi import com.tangem.tap.features.FragmentOnBackPressedHandler import com.tangem.tap.features.addBackPressHandler import com.tangem.tap.features.onboarding.products.wallet.redux.* -import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletMiddleware.Companion.BUY_WALLET_URL import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog import com.tangem.tap.store import com.tangem.wallet.R @@ -62,6 +61,8 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), store.dispatch(OnboardingWalletAction.Init) toolbar.setNavigationOnClickListener { activity?.onBackPressed() } + store.dispatch(OnboardingWalletAction.LoadArtwork) + addBackPressHandler(this) } @@ -84,7 +85,10 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), requireActivity().invalidateOptionsMenu() - state.artwork?.artwork?.let { imv_front_card.setImageBitmap(it) } + loadImageIntoImageView(state.cardArtworkUrl, imv_front_card) + loadImageIntoImageView(state.cardArtworkUrl, imv_first_backup_card) + loadImageIntoImageView(state.cardArtworkUrl, imv_second_backup_card) + pb_state.max = 6 pb_state.progress = state.getProgressStep() @@ -99,6 +103,14 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), } } + private fun loadImageIntoImageView(url: String?, view: ImageView) { + Picasso.get() + .load(url) + .error(R.drawable.card_placeholder_black) + .placeholder(R.drawable.card_placeholder_black) + ?.into(view) + } + private fun setupCreateWalletState() { btn_main_action.setText(R.string.onboarding_create_wallet_button_create_wallet) btn_main_action.setOnClickListener { store.dispatch(OnboardingWalletAction.CreateWallet) } @@ -240,15 +252,11 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), private fun showEnterAccessCode(state: BackupState) { accessCodeDialog?.showEnterAccessCode() accessCodeDialog?.showError(state.accessCodeError) - postUi(2000) { store.dispatch(BackupAction.SetAccessCodeError(null)) } - } private fun showReenterAccessCode(state: BackupState) { accessCodeDialog?.showReenterAccessCode() accessCodeDialog?.showError(state.accessCodeError) - postUi(2000) { store.dispatch(BackupAction.SetAccessCodeError(null)) } - } private fun showWritePrimaryCard(state: BackupState) { @@ -312,7 +320,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), tv_header.text = getString(R.string.onboarding_title_backup_card_format, cardNumber) tv_body.text = getString( - R.string.onboarding_subtitle_scan_primary_card_format, + R.string.onboarding_subtitle_scan_backup_card_format, cardIdFormatter.getFormattedCardId(state.backupCardIds[cardNumber - 1]) ) btn_main_action.text = getString( @@ -337,7 +345,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), tab_layout_backup_info.hide() tv_body.text = text - btn_main_action.text = getText(R.string.common_continue) + btn_main_action.text = getText(R.string.onboarding_button_continue_wallet) btn_alternative_action.hide() btn_main_action.setOnClickListener { lav_confetti.cancelAnimation() @@ -362,7 +370,7 @@ class OnboardingWalletFragment : Fragment(R.layout.fragment_onboarding_wallet), override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.shop_menu -> { - store.dispatchOpenUrl(BUY_WALLET_URL) + store.dispatch(BackupAction.GoToShop) true } else -> super.onOptionsItemSelected(item) diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index ff45a74e19..572658b793 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -9,8 +9,9 @@ import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError import com.tangem.common.services.Result +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.analytics.AnalyticsEvent -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.analytics.AnalyticsParam import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -180,7 +181,7 @@ private fun sendTransaction( tangemSdk.config.linkedTerminal = isLinkedTerminal when (sendResult) { is SimpleResult.Success -> { - FirebaseAnalyticsHandler.triggerEvent( + store.state.globalState.analyticsHandlers?.triggerEvent( event = AnalyticsEvent.TRANSACTION_IS_SENT, card = card, blockchain = walletManager.wallet.blockchain.currency @@ -204,13 +205,13 @@ private fun sendTransaction( } is SimpleResult.Failure -> { when (sendResult.error) { - is CreateAccountUnderfunded -> { - val error = sendResult.error as CreateAccountUnderfunded + is BlockchainSdkError.CreateAccountUnderfunded -> { + val error = sendResult.error as BlockchainSdkError.CreateAccountUnderfunded val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0" val symbol = error.minReserve.currencySymbol dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol)))) } - is SendException -> { + is BlockchainSdkError.SendException -> { sendResult.error?.let { FirebaseCrashlytics.getInstance().recordException(it) } } is Throwable -> { @@ -239,11 +240,11 @@ private fun sendTransaction( } else -> { (sendResult.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + store.state.globalState.analyticsHandlers?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.SendTransaction, + Analytics.ActionToLog.SendTransaction, mapOf( - FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN + AnalyticsParam.BLOCKCHAIN to walletManager.wallet.blockchain.currency), card = card, ) diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index b90c53e080..894d9809ba 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -137,6 +137,11 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber adapter.submitList(state.sendWarningsList) rv.show(state.sendWarningsList.isNotEmpty()) + + fg.toolbar.title = fg.getString( + R.string.send_title_currency_format, + state.amountState.mainCurrency.currencySymbol + ) } private fun handleAddressPayIdState(fg: BaseStoreFragment, state: AddressPayIdState) { diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt index 53acef169a..032f9d82ef 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/CurrencyListItem.kt @@ -23,29 +23,35 @@ sealed class CurrencyListItem { blockchains: List, tokens: List, ): List { - val blockchainsTitle = R.string.add_tokens_subtitle_blockchains - val ethereumTokensTitle = R.string.add_tokens_subtitle_ethereum_tokens - val bscTokensTitle = R.string.add_tokens_subtitle_bsc_tokens - val binanceTokensTitle = R.string.add_tokens_subtitle_binance_tokens + return createBlockchainList(blockchains) + + createTokensList(R.string.add_tokens_subtitle_ethereum_tokens, Blockchain.Ethereum, tokens) + + createTokensList(R.string.add_tokens_subtitle_bsc_tokens, Blockchain.BSC, tokens) + + createTokensList(R.string.add_tokens_subtitle_binance_tokens, Blockchain.Binance, tokens) + + createTokensList(R.string.add_tokens_subtitle_avalanche_tokens, Blockchain.Avalanche, tokens) + } - val ethereumTokens = tokens.filter { it.blockchain == Blockchain.Ethereum } - val bscTokens = tokens.filter { it.blockchain == Blockchain.BSC } - val binanceTokens = tokens.filter { it.blockchain == Blockchain.Binance } - return listOf(TitleListItem(blockchainsTitle)) + - blockchains.map { BlockchainListItem(it) } + - listOf(TitleListItem(ethereumTokensTitle, blockchain = Blockchain.Ethereum)) + - ethereumTokens.map { TokenListItem(it) } + - listOf(TitleListItem(bscTokensTitle, blockchain = Blockchain.BSC)) + - bscTokens.map { TokenListItem(it) } + - listOf(TitleListItem(binanceTokensTitle, blockchain = Blockchain.Binance)) + - binanceTokens.map { TokenListItem(it) } + private fun createBlockchainList(blockchains: List): List { + val blockchainsTitle = R.string.add_tokens_subtitle_blockchains + return listOf(TitleListItem(blockchainsTitle)) + blockchains.map { BlockchainListItem(it) } + } + + private fun createTokensList( + @StringRes titleResId: Int, + blockchain: Blockchain, + tokens: List + ): List { + val filteredTokens = tokens.filter { + it.blockchain == blockchain || it.blockchain == blockchain.getTestnetVersion() + } + val tokensListItem = filteredTokens.map { TokenListItem(it) } + return listOf(TitleListItem(titleResId, blockchain = blockchain)) + tokensListItem } } } fun List.removeTokensForBlockchain(blockchain: Blockchain): List { return filterNot { - it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain + it is CurrencyListItem.TokenListItem && it.isTheSameOrTestnetBlockchain(blockchain) } } @@ -53,18 +59,31 @@ fun List.addTokensForBlockchain( blockchain: Blockchain, fullCurrenciesList: List, ): List { val tokensToAdd = fullCurrenciesList.filter { - it is CurrencyListItem.TokenListItem && it.token.blockchain == blockchain + it is CurrencyListItem.TokenListItem && it.isTheSameOrTestnetBlockchain(blockchain) } val indexToInsert = indexOfFirst { - it is CurrencyListItem.TitleListItem && it.blockchain == blockchain + it is CurrencyListItem.TitleListItem && it.isTheSameOrTestnetBlockchain(blockchain) } + 1 return this.toMutableList().apply { addAll(indexToInsert, tokensToAdd) } } fun List.toggleHeaderContentShownValue(blockchain: Blockchain) { map { - if (it is CurrencyListItem.TitleListItem && it.blockchain == blockchain) { + if (it is CurrencyListItem.TitleListItem && it.isTheSameOrTestnetBlockchain(blockchain)) { it.isContentShown = !it.isContentShown } } +} + +// if you do not check the testNet blockchains, it will not collapse +private fun CurrencyListItem.isTheSameOrTestnetBlockchain(blockchain: Blockchain): Boolean { + return when (this) { + is CurrencyListItem.TitleListItem -> this.blockchain?.isTheSameOrTestnetBlockchain(blockchain) ?: false + is CurrencyListItem.BlockchainListItem -> this.blockchain.isTheSameOrTestnetBlockchain(blockchain) + is CurrencyListItem.TokenListItem -> this.token.blockchain.isTheSameOrTestnetBlockchain(blockchain) + } +} + +private fun Blockchain.isTheSameOrTestnetBlockchain(blockchain: Blockchain): Boolean { + return this == blockchain || this == blockchain.getTestnetVersion() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt index 30db14cfbe..30cec235f8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/redux/TokensMiddleware.kt @@ -4,11 +4,11 @@ import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Token import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve +import com.tangem.common.extensions.ByteArrayKey +import com.tangem.common.extensions.toMapKey import com.tangem.common.hdWallet.DerivationPath -import com.tangem.common.hdWallet.ExtendedPublicKey -import com.tangem.tap.common.extensions.ByteArrayKey +import com.tangem.operations.derivation.ExtendedPublicKeysMap import com.tangem.tap.common.extensions.dispatchErrorNotification -import com.tangem.tap.common.extensions.toMapKey import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.common.redux.navigation.NavigationAction @@ -32,7 +32,7 @@ class TokensMiddleware { { next -> { action -> when (action) { - is TokensAction.LoadCurrencies -> handleLoadCurrencies(action) + is TokensAction.LoadCurrencies -> handleLoadCurrencies() is TokensAction.SaveChanges -> handleSaveChanges(action) } next(action) @@ -40,7 +40,7 @@ class TokensMiddleware { } } - private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) { + private fun handleLoadCurrencies() { val scanResponse = store.state.globalState.scanResponse ?: return val isTestcard = scanResponse.card.isTestCard @@ -48,7 +48,7 @@ class TokensMiddleware { val blockchains = currenciesRepository.getBlockchains( cardFirmware = scanResponse.card.firmwareVersion, isTestNet = isTestcard - ) + ).sortedBy { it.fullName } val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList() store.dispatch(TokensAction.LoadCurrencies.Success(currencies)) @@ -94,14 +94,14 @@ class TokensMiddleware { is CompletionResult.Success -> { val newDerivedKeys = result.data.entries val updatedDerivedKeys = - mutableMapOf>() + mutableMapOf() newDerivedKeys.forEach { entry -> val derivationData = derivationDataList.find { it.mapKeyOfWalletPublicKey == entry.key } ?: return@forEach updatedDerivedKeys[entry.key] = - derivationData.alreadyDerivedKeys + entry.value.toList() + ExtendedPublicKeysMap(derivationData.alreadyDerivedKeys + entry.value) } val updatedScanResponse = scanResponse.copy( @@ -133,9 +133,9 @@ class TokensMiddleware { .mapNotNull { it.derivationPath() } val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey() - val alreadyDerivedKeys = - scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf() - val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath } + val alreadyDerivedKeys: ExtendedPublicKeysMap = + scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap()) + val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList() val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) } return DerivationData( @@ -147,7 +147,7 @@ class TokensMiddleware { private class DerivationData( val derivations: Pair>, - val alreadyDerivedKeys: List, + val alreadyDerivedKeys: ExtendedPublicKeysMap, val mapKeyOfWalletPublicKey: ByteArrayKey ) diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 1081a3303c..029b6b88b5 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -146,4 +146,5 @@ sealed class WalletAction : Action { data class ChangeSelectedAddress(val type: AddressType) : WalletAction() + data class SetWalletRent(val blockchain: Blockchain, val minRent: String, val rentExempt: String): WalletAction() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index 96044f0e1c..4dd980e98c 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -246,7 +246,8 @@ data class WalletData( val fiatRateString: String? = null, val fiatRate: BigDecimal? = null, val mainButton: WalletMainButton = WalletMainButton.SendButton(false), - val currency: Currency + val currency: Currency, + val walletRent: WalletRent? = null, ) { fun shouldShowMultipleAddress(): Boolean { val listOfAddresses = walletAddresses?.list ?: return false @@ -254,6 +255,11 @@ data class WalletData( } } +data class WalletRent( + val minRentValue: String, + val rentExemptValue: String +) + sealed interface Currency { val blockchain: com.tangem.blockchain.common.Blockchain diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt index ace2084261..df4c9120f7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/MultiWalletMiddleware.kt @@ -1,10 +1,12 @@ package com.tangem.tap.features.wallet.redux.middlewares +import com.tangem.blockchain.blockchains.solana.RentProvider import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.Result import com.tangem.common.extensions.isZero import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.safeUpdate +import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction @@ -31,6 +33,7 @@ class MultiWalletMiddleware { when (action) { is WalletAction.MultiWallet.AddWalletManagers -> { globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers) + action.walletManagers.forEach { checkForRentWarning(it) } } is WalletAction.MultiWallet.SelectWallet -> { if (action.walletData != null) { @@ -132,7 +135,7 @@ class MultiWalletMiddleware { val walletManager = walletState?.getWalletManager(Blockchain.Ethereum) ?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum) - val tokenFinder = walletManager as TokenFinder + val tokenFinder = walletManager as? TokenFinder ?: return scope.launch { val result = tokenFinder.findTokens() withContext(Dispatchers.Main) { @@ -197,6 +200,26 @@ class MultiWalletMiddleware { } } + private fun checkForRentWarning(walletManager: WalletManager) { + val rentProvider = walletManager as? RentProvider ?: return + + scope.launch { + when (val result = rentProvider.minimalBalanceForRentExemption()) { + is Result.Success -> { + val currency = walletManager.wallet.blockchain.currency + val minRent = ("${rentProvider.rentAmount().stripZeroPlainString()} $currency") + val rentExempt = ("${result.data.stripZeroPlainString()} $currency") + store.dispatchOnMain(WalletAction.SetWalletRent( + blockchain = walletManager.wallet.blockchain, + minRent = minRent, + rentExempt = rentExempt + )) + } + is Result.Failure -> {} + } + } + } + private fun addTokens( tokens: List, walletState: WalletState?, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt index a33d9dbb41..37b71f1c2a 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletMiddleware.kt @@ -10,7 +10,7 @@ import com.tangem.common.extensions.isZero import com.tangem.common.services.Result import com.tangem.operations.attestation.OnlineCardVerifier import com.tangem.tap.* -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler +import com.tangem.tap.common.analytics.Analytics import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState @@ -104,13 +104,13 @@ class WalletMiddleware { when (result) { is CompletionResult.Success -> { val scanNoteResponse = globalState.scanResponse?.copy(card = result.data) - scanNoteResponse?.let { store.onCardScanned(scanNoteResponse, false) } + scanNoteResponse?.let { store.onCardScanned(scanNoteResponse) } } is CompletionResult.Failure -> { (result.error as? TangemSdkError)?.let { error -> - FirebaseAnalyticsHandler.logCardSdkError( + store.state.globalState.analyticsHandlers?.logCardSdkError( error, - FirebaseAnalyticsHandler.ActionToLog.CreateWallet, + Analytics.ActionToLog.CreateWallet, card = store.state.detailsState.scanResponse?.card ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt index 7dacae94b1..408f0ef8a3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt @@ -7,7 +7,6 @@ import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.card.Card import com.tangem.common.card.FirmwareVersion import com.tangem.tap.common.analytics.AnalyticsEvent -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.isGreaterThan import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.domain.TapWorkarounds.isTestCard @@ -75,7 +74,7 @@ class WarningsMiddleware { preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds() } if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) { - FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED) + store.state.globalState.analyticsHandlers?.triggerEvent(AnalyticsEvent.APP_RATING_DISPLAYED) addWarningMessage(WarningMessagesManager.appRatingWarning(), true) } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 097fbf28e3..7005235044 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -245,6 +245,17 @@ private fun internalReduce(action: Action, state: AppState): WalletState { ) newState = newState.copy(wallets = wallets) } + is WalletAction.SetWalletRent -> { + var walletData = newState.getWalletData(action.blockchain) + if (walletData == null) { + newState + } else { + walletData = walletData.copy(walletRent = WalletRent(action.minRent, action.rentExempt)) + newState = newState.copy( + wallets = newState.replaceSomeWallets(listOf(walletData)) + ) + } + } } return newState } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 41a3dc9258..bfd6057d73 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.TransitionInflater import com.squareup.picasso.Picasso +import com.tangem.common.extensions.guard import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.StateDialog @@ -27,6 +28,8 @@ import kotlinx.android.synthetic.main.item_popular_token.view.* import kotlinx.android.synthetic.main.layout_balance_error.* import kotlinx.android.synthetic.main.layout_balance_wallet_details.* import kotlinx.android.synthetic.main.layout_wallet_details.* +import kotlinx.android.synthetic.main.layout_wallet_details.card_balance +import kotlinx.android.synthetic.main.layout_wallet_details_rent.* import org.rekotlin.StoreSubscriber class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreSubscriber { @@ -124,11 +127,12 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS handleDialogs(state.walletDialog) handleCurrencyIcon(selectedWallet) + handleWalletRent(selectedWallet.walletRent) srl_wallet_details.setOnRefreshListener { if (selectedWallet.currencyData.status != BalanceStatus.Loading) { store.dispatch(WalletAction.LoadWallet( - blockchain = selectedWallet.currency?.blockchain + blockchain = selectedWallet.currency.blockchain )) } } @@ -141,6 +145,18 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), StoreS btn_sell.show(selectedWallet.tradeCryptoState.sellingAllowed) } + private fun handleWalletRent(rent: WalletRent?) { + val rent = rent.guard { + l_rent_warning.hide() + return + } + + val warningMessage = requireContext().getString( + R.string.solana_rent_warning, rent.minRentValue, rent.rentExemptValue) + tv_rent_warning_message.text = warningMessage + l_rent_warning.show() + } + private fun handleCurrencyIcon(wallet: WalletData) { Picasso.get().loadCurrenciesIcon( imageView = iv_currency, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt index 2b793466c3..22c2d56cf3 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/adapters/WarningMessagesAdapter.kt @@ -10,7 +10,6 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ItemDecoration import com.google.android.play.core.review.ReviewManagerFactory import com.tangem.tap.common.analytics.AnalyticsEvent -import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler import com.tangem.tap.common.extensions.getActivity import com.tangem.tap.common.extensions.getString import com.tangem.tap.common.extensions.hide @@ -102,13 +101,14 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) { WarningMessage.Type.AppRating -> { view.group_controls_temporary.hide() view.group_controls_rating.show() + val analyticsHandler = store.state.globalState.analyticsHandlers view.btn_close.setOnClickListener { - FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS) + analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_DISMISS) store.dispatch(GlobalAction.HideWarningMessage(warning)) store.dispatch(WalletAction.Warnings.AppRating.RemindLater) } view.btn_can_be_better.setOnClickListener { - FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE) + analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_NEGATIVE) store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) store.dispatch(GlobalAction.HideWarningMessage(warning)) store.dispatch(GlobalAction.SendFeedback(RateCanBeBetterEmail())) @@ -116,7 +116,7 @@ class WarningMessageVH(val view: View) : RecyclerView.ViewHolder(view) { view.btn_really_cool.setOnClickListener { val activity = view.context.getActivity() ?: return@setOnClickListener - FirebaseAnalyticsHandler.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE) + analyticsHandler?.triggerEvent(AnalyticsEvent.APP_RATING_POSITIVE) store.dispatch(WalletAction.Warnings.AppRating.SetNeverToShow) val reviewManager = ReviewManagerFactory.create(activity) val task = reviewManager.requestReviewFlow() diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml index 7f004c814d..5df3b30845 100644 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -78,28 +78,38 @@ @@ -182,6 +192,7 @@ app:tabBackground="@drawable/selector_tabs_dots" app:tabIndicator="@null" app:tabPaddingStart="8dp" + android:layout_marginBottom="16dp" app:tabPaddingEnd="8dp" app:tabGravity="center"/> diff --git a/app/src/main/res/layout/fragment_wallet_details.xml b/app/src/main/res/layout/fragment_wallet_details.xml index 27da2ac50f..0f6ef2b42d 100644 --- a/app/src/main/res/layout/fragment_wallet_details.xml +++ b/app/src/main/res/layout/fragment_wallet_details.xml @@ -21,9 +21,9 @@ android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" + android:theme="@style/ThemeOverlay.MyTheme.Toolbar" app:menu="@menu/wallet_details" app:navigationIcon="@drawable/ic_baseline_arrow_back_24" - android:theme="@style/ThemeOverlay.MyTheme.Toolbar" app:title=" " /> @@ -46,7 +46,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - + - - + + diff --git a/app/src/main/res/layout/layout_backup_access_code_info.xml b/app/src/main/res/layout/layout_backup_access_code_info.xml index fab2ddf46d..ac5a98171f 100644 --- a/app/src/main/res/layout/layout_backup_access_code_info.xml +++ b/app/src/main/res/layout/layout_backup_access_code_info.xml @@ -122,7 +122,7 @@ android:layout_marginStart="78dp" android:layout_marginEnd="78dp" android:layout_marginBottom="92dp" - android:text="@string/common_create" + android:text="@string/common_continue" android:textSize="16sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/layout_backup_access_code_submit.xml b/app/src/main/res/layout/layout_backup_access_code_submit.xml index eca2c05bbd..c9416e28f1 100644 --- a/app/src/main/res/layout/layout_backup_access_code_submit.xml +++ b/app/src/main/res/layout/layout_backup_access_code_submit.xml @@ -27,7 +27,7 @@ android:layout_marginTop="44dp" android:layout_marginEnd="16dp" android:layout_marginBottom="40dp" - android:hint="Access code" + android:hint="@string/onboarding_wallet_info_title_third" app:boxStrokeColor="@color/selector_edit_text" app:boxStrokeWidth="1dp" app:endIconDrawable="@drawable/selector_password_toggle" @@ -53,6 +53,9 @@ android:layout_marginStart="78dp" android:layout_marginEnd="78dp" android:layout_marginBottom="92dp" + android:layout_marginTop="32dp" + app:layout_constraintTop_toTopOf="@id/til_access_code" + app:layout_constraintVertical_bias="1" android:text="@string/common_create" android:textSize="16sp" app:layout_constraintBottom_toBottomOf="parent" /> diff --git a/app/src/main/res/layout/layout_wallet_details_rent.xml b/app/src/main/res/layout/layout_wallet_details_rent.xml new file mode 100644 index 0000000000..1f44d10ea7 --- /dev/null +++ b/app/src/main/res/layout/layout_wallet_details_rent.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index faad406cb3..e77a33cb2b 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -27,7 +27,7 @@ Preparing card Creating wallet The wallet creation procedure consists of three steps. You must complete it to the end, otherwise you will have to start from the beginning. - Tap the card #%s + Scan the card #%s You have already started the process of recreating twin wallet. If you interrupt it, you won\'t be able to use your twin cards until you start it again and complete recreating the wallet. You\'ve tapped the same card. To create twin wallet you need to tap a second twin card. @@ -101,6 +101,7 @@ Ethereum tokens Binance Smart Chain tokens Binance Chain tokens + Avalanche C-Chain tokens Copy Share + Add tokens @@ -294,4 +295,23 @@ Access code is available only for cards with a backup. + + + The BNB transaction has been successfully signed and sent to the Dapp + Dapp %s, requesting to\n +sign BNB transaction with\n +Card: %s\n +\n +%s + Transaction details:\n +From: %s\n +To: %s\n +Amount: %s + Trade order for %s\n +Price: %s\n +Amount to receive: %s\n +Amount to pay: %s + + Solana network charges a rent of %s every 2 days. Accounts that can’t afford the rent are purged from the network. Deposit your account with more than %s to use it for free. + diff --git a/dependencies.gradle b/dependencies.gradle index 43b263845b..8004a23445 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,4 +1,4 @@ ext.versions = [ - kotlin : '1.5.30', - build_gradle: '7.0.2', + kotlin : '1.6.10', + build_gradle: '7.0.4', ]